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

# Conditional Permissions

> Dynamic access control with JSON Logic expressions

## What are Conditional Permissions?

**Conditional permissions** allow you to add dynamic rules to your access control using [JSON Logic](https://jsonlogic.com/) expressions. Instead of simple allow/deny, you can create permissions that evaluate based on:

* Subject attributes (department, clearance level, role metadata)
* Resource properties (owner, status, tags)
* Request context (time, IP address, device type)
* Custom business logic

## Where Conditions Apply

Conditions can be added at multiple levels:

| Level               | Interface                                      | Use Case                                    |
| ------------------- | ---------------------------------------------- | ------------------------------------------- |
| **Role-Permission** | `BedrockRolePermission.condition`              | "Editors can only edit draft documents"     |
| **Scope Override**  | `BedrockScopeRolePermissionOverride.condition` | "In production, only during business hours" |
| **Resource Policy** | `BedrockResourcePolicy.subjectCondition`       | "Only finance team can access this report"  |
| **Resource Policy** | `BedrockResourcePolicy.contextCondition`       | "Only from approved IP ranges"              |

## JSON Logic Basics

JSON Logic uses a simple JSON structure to express conditions:

```json theme={null}
{ "operator": [arguments] }
```

### Common Operators

| Operator             | Description         | Example                                                        |
| -------------------- | ------------------- | -------------------------------------------------------------- |
| `==`                 | Equals              | `{"==": [{"var": "subject.subjectType"}, "admin"]}`            |
| `!=`                 | Not equals          | `{"!=": [{"var": "resource.status"}, "archived"]}`             |
| `>`, `>=`, `<`, `<=` | Comparisons         | `{">=": [{"var": "subject.level"}, 3]}`                        |
| `in`                 | Value in array      | `{"in": [{"var": "subject.dept"}, ["finance", "accounting"]]}` |
| `and`                | All conditions true | `{"and": [cond1, cond2]}`                                      |
| `or`                 | Any condition true  | `{"or": [cond1, cond2]}`                                       |
| `!`                  | Negation            | `{"!": [condition]}`                                           |
| `if`                 | Conditional         | `{"if": [cond, then, else]}`                                   |

### The `var` Operator

Use `var` to access values from the evaluation context:

```json theme={null}
{"var": "subject.meta.department"}
{"var": "tags.classification"}
{"var": "time.hour"}
```

## Conditional Role-Permissions

Add conditions when assigning permissions to roles:

```bash theme={null}
# Editors can only edit documents they own
curl -X POST 'https://api.example.com/role-permissions' \
  -H 'Content-Type: application/json' \
  -d '{
    "roleId": "role_editor",
    "permissionId": "perm_document_edit",
    "condition": {
      "==": [
        {"var": "subject.id"},
        {"var": "tags.ownerId"}
      ]
    }
  }'
```

<Note>
  The resource object does **not** expose an owner subject id. To match a
  resource's owner against the acting subject, surface the owner id as a
  resource **tag** (read via `tags.<identifier>`) or pass it in your own
  `context`. `resource.ownerScopeId` identifies the owning **scope**, not a
  subject.
</Note>

```bash theme={null}
# Managers can approve expenses under $10,000.
# `resource.amount` is NOT exposed in the context (see Resource Variables below) —
# pass the amount in `context` and reference it at the top level.
curl -X POST 'https://api.example.com/role-permissions' \
  -d '{
    "roleId": "role_manager",
    "permissionId": "perm_expense_approve",
    "condition": {
      "<": [{"var": "amount"}, 10000]
    }
  }'
```

## Conditional Scope Overrides

Add conditions to scope-level overrides:

```bash theme={null}
# In production, only allow deploys during business hours (a role-permission override)
curl -X POST 'https://api.example.com/scope-overrides/role-permissions' \
  -d '{
    "childScopeId": "scope_production",
    "roleId": "role_developer",
    "permissionId": "perm_deploy",
    "state": "grant",
    "condition": {
      "and": [
        {">=": [{"var": "time.hour"}, 9]},
        {"<=": [{"var": "time.hour"}, 17]},
        {"in": [{"var": "time.dayOfWeek"}, [1, 2, 3, 4, 5]]}
      ]
    }
  }'
```

## Context Variables

The evaluation context provides these variables. The engine resolves
`subject` from the actor, and—when a resource is supplied—`resource`, `tags`,
and `tagList`. These resolved values **overwrite** any same-named keys you pass
in `context`.

### Subject Variables

The full resolved subject is exposed under `subject`:

| Variable              | Description                              |
| --------------------- | ---------------------------------------- |
| `subject.id`          | Bedrock subject ID                       |
| `subject.subjectType` | Subject type (e.g. user, agent, service) |
| `subject.externalId`  | Your system's identifier for the subject |
| `subject.displayName` | Human-readable name (if set)             |
| `subject.meta.*`      | Custom metadata fields on the subject    |

### Resource Variables

Available only when the evaluation includes a resource. These are the **only**
resource fields exposed:

| Variable                      | Description                                |
| ----------------------------- | ------------------------------------------ |
| `resource.id`                 | Bedrock resource ID                        |
| `resource.externalResourceId` | Your system's identifier for the resource  |
| `resource.resourceType`       | Resource type key (e.g. `"document"`)      |
| `resource.resourceTypeId`     | Resource type ID                           |
| `resource.ownerScopeId`       | ID of the **scope** that owns the resource |
| `resource.displayName`        | Human-readable name (if set)               |

<Note>
  There is no `resource.meta` or `resource.ownerId`, and arbitrary resource
  columns are **not** auto-exposed. To use custom resource attributes (owner
  subject, amount, status, classification, etc.) in a condition, surface them
  as **tags** (`tags.*` / `tagList`) or pass them yourself in `context`.
</Note>

### Tag Variables

Loaded when a resource is resolved and `includeResourceTags` is `true` (the
default):

| Variable            | Description                                                                  |
| ------------------- | ---------------------------------------------------------------------------- |
| `tags.<identifier>` | The **label** of the tag with that identifier (a string)                     |
| `tagList`           | Array of `{ id, identifier, label, groupId }`—use for `in`/membership checks |

### Time (trusted clock)

The engine injects the current time from a **trusted server clock** at the top level. Any `time`, `now`, or `nowMs` you pass in `context` is **ignored**, so a caller cannot forge the time to slip past a time-based condition:

| Variable         | Description                               |
| ---------------- | ----------------------------------------- |
| `time.hour`      | Current hour, 0–23 (UTC)                  |
| `time.minute`    | Current minute, 0–59 (UTC)                |
| `time.dayOfWeek` | Day of week, 0=Sun … 6=Sat (UTC)          |
| `now` / `nowMs`  | Current time as ISO string / epoch millis |

### Your Context

Any other keys you pass in `context` are spread at the **top level** (not under a `context.` prefix). The resolved `subject`, `resource`, `tags`, and `tagList` overwrite same-named keys:

| Variable      | Description                                                                      |
| ------------- | -------------------------------------------------------------------------------- |
| `ip`          | e.g. a request IP you supply as `context: { ip }`, referenced as `{"var": "ip"}` |
| `<your keys>` | Any custom context you provide, referenced by its own top-level name             |

## Common Patterns

### Department-Based Access

```json theme={null}
{
  "in": [
    {"var": "subject.meta.department"},
    {"var": "tags.allowedDepartments"}
  ]
}
```

### Clearance Level

```json theme={null}
{
  ">=": [
    {"var": "subject.meta.clearanceLevel"},
    {"var": "tags.requiredClearance"}
  ]
}
```

### Owner-Only Access

```json theme={null}
{
  "==": [
    {"var": "subject.id"},
    {"var": "tags.ownerId"}
  ]
}
```

### Business Hours Only

```json theme={null}
{
  "and": [
    {">=": [{"var": "time.hour"}, 9]},
    {"<=": [{"var": "time.hour"}, 17]},
    {"!": {"in": [{"var": "time.dayOfWeek"}, [0, 6]]}}
  ]
}
```

### IP Allowlist

```json theme={null}
{
  "in": [
    {"var": "ip"},
    ["192.168.1.10", "10.0.0.5"]
  ]
}
```

### Combined Conditions

```json theme={null}
{
  "or": [
    {"==": [{"var": "subject.subjectType"}, "admin"]},
    {
      "and": [
        {"==": [{"var": "subject.meta.department"}, "finance"]},
        {">=": [{"var": "subject.meta.level"}, 3]}
      ]
    }
  ]
}
```

## Evaluation Behavior

When a permission has a condition:

1. **No condition** → Permission always applies
2. **Condition evaluates to `true`** → Permission applies
3. **Condition evaluates to `false`** → Permission does not apply
4. **Condition errors** → Permission does not apply (fail-closed)

```typescript theme={null}
// Evaluation with context. `time` comes from the trusted server clock — anything
// you pass as time/now/nowMs is ignored, so pass only your own data (e.g. ip).
const decision = await bedrock.evaluate({
  actor: { subjectId: "subject_jane" },
  scopeId: "scope_production",
  action: "deploy",
  context: {
    ip: "192.168.1.100"
  }
});
```

## Debugging Conditions

The decision output includes condition evaluation details:

```typescript theme={null}
const decision = await bedrock.evaluate(input);

console.log(decision.matches);
// Shows which permissions matched and their conditions

console.log(decision.evaluatedContext);
// Shows the full context used for evaluation

console.log(decision.explanation);
// Human-readable explanation of the decision
```

## Best Practices

<AccordionGroup>
  <Accordion title="Keep conditions simple">
    Complex nested conditions are hard to debug. Break them into multiple permissions if needed.
  </Accordion>

  <Accordion title="Test conditions thoroughly">
    Create test cases for all condition branches before deploying.
  </Accordion>

  <Accordion title="Use meaningful variable names">
    Structure your metadata with clear, consistent naming.
  </Accordion>

  <Accordion title="Document your conditions">
    Add descriptions to permissions explaining what the condition does.
  </Accordion>

  <Accordion title="Fail closed">
    If a condition can't be evaluated (missing data), access is denied by default.
  </Accordion>
</AccordionGroup>

## Related Concepts

<CardGroup cols={2}>
  <Card title="Resource Policies" icon="shield-check" href="/resources/resource-policies">
    Fine-grained policies with subject and context conditions
  </Card>

  <Card title="Evaluation" icon="gears" href="/concepts/evaluation">
    How conditions are evaluated in the permission check flow
  </Card>
</CardGroup>
