> ## 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.

# Row-Level Access Control

> Fine-grained permissions for individual resources

## Overview

Row-level access control (RLAC) restricts access to individual records based on attributes of the user and the record. Instead of "can read all documents", it's "can read documents they own" or "can read documents in their department".

<Warning>
  **Several patterns below are not enforced yet.** A json-logic condition can only read what the
  engine puts in the context. Today that is: the acting **subject** (`subject.id`,
  `subject.subjectType`, `subject.meta.*`), the **resource**'s core fields (`resource.id`,
  `resource.resourceType`, `resource.ownerScopeId`, `resource.displayName`), the resource's
  **tags** (top-level `tags` / `tagList`), the trusted clock (`time.*`), and any top-level custom
  `context` keys. It does **not** yet expose `subject.tags.*`, `resource.createdBy`,
  `resource.ownerId`, or `resource.meta.*`, so patterns using those **never grant today** (see the
  [roadmap](/roadmap)). Two things that work now: for **owner-based** access use resource
  **ownership** (an `owns(subject, resource, role)` grant) rather than a `resource.createdBy`
  condition; for attribute rules use resource **tags** and `subject.meta.*`. Also, conditions
  attach to the **role-permission edge** (`condition`), not a permission `logic` field.
</Warning>

## Patterns

### Pattern 1: Owner-Based Access

Users can only access resources they own:

```bash theme={null}
# Create permission with owner condition
curl -X POST 'https://api.example.com/permissions' \
  -d '{
    "scopeId": "scope_org",
    "action": "read",
    "resourceType": "document",
    "resourcePattern": "*",
    "key": "document:read:owned",
    "label": "Read Own Documents",
    "logic": {
      "==": [{"var": "resource.createdBy"}, {"var": "subject.id"}]
    }
  }'
```

```typescript theme={null}
// Evaluation
const decision = await bedrock.evaluate({
  actor: { subjectId: "subject_jane", subjectType: "user" },
  scopeId: "scope_org",
  action: "read",
  resource: { resourceId: "resource_doc_123" }  // createdBy: "subject_jane"
});
// Result: ALLOWED (Jane owns this document)
```

### Pattern 2: Department-Based Access

Users can access resources in their department:

```bash theme={null}
# Tag users with departments
curl -X POST 'https://api.example.com/tag-assignments' \
  -d '{"tagId": "tag_engineering", "targetType": "subject", "targetId": "subject_jane"}'

# Tag resources with departments
curl -X POST 'https://api.example.com/tag-assignments' \
  -d '{"tagId": "tag_engineering", "targetType": "resource", "targetId": "resource_doc_123"}'

# Permission: Read documents in your department
curl -X POST 'https://api.example.com/permissions' \
  -d '{
    "scopeId": "scope_org",
    "action": "read",
    "resourceType": "document",
    "resourcePattern": "*",
    "key": "document:read:department",
    "logic": {
      "some": [
        {"var": "resource.tags.departments"},
        {"in": [{"var": ""}, {"var": "subject.tags.departments"}]}
      ]
    }
  }'
```

### Pattern 3: Manager Access

Managers can access their reports' data:

```bash theme={null}
# Store reports in subject metadata
curl -X PATCH 'https://api.example.com/subjects/subject_manager' \
  -d '{
    "meta": {
      "directReports": ["subject_alice", "subject_bob", "subject_charlie"]
    }
  }'

# Permission: Read reports' timesheets
curl -X POST 'https://api.example.com/permissions' \
  -d '{
    "scopeId": "scope_org",
    "action": "read",
    "resourceType": "timesheet",
    "resourcePattern": "*",
    "key": "timesheet:read:reports",
    "logic": {
      "in": [{"var": "resource.ownerId"}, {"var": "subject.meta.directReports"}]
    }
  }'
```

### Pattern 4: Project Team Access

Only project team members can access project resources:

```bash theme={null}
# Tag users with projects
curl -X POST 'https://api.example.com/tag-assignments/batch' \
  -d '[
    {"tagId": "tag_project_alpha", "targetType": "subject", "targetId": "subject_jane"},
    {"tagId": "tag_project_alpha", "targetType": "subject", "targetId": "subject_bob"}
  ]'

# Tag resources with projects
curl -X POST 'https://api.example.com/tag-assignments' \
  -d '{"tagId": "tag_project_alpha", "targetType": "resource", "targetId": "resource_spec_doc"}'

# Permission
curl -X POST 'https://api.example.com/permissions' \
  -d '{
    "scopeId": "scope_org",
    "action": "read",
    "resourceType": "project-doc",
    "resourcePattern": "*",
    "key": "project-doc:read:team",
    "logic": {
      "some": [
        {"var": "resource.tags.projects"},
        {"in": [{"var": ""}, {"var": "subject.tags.projects"}]}
      ]
    }
  }'
```

### Pattern 5: Geographic Restrictions

Access based on region:

```bash theme={null}
# Permission: Access customer data in your region
curl -X POST 'https://api.example.com/permissions' \
  -d '{
    "scopeId": "scope_org",
    "action": "read",
    "resourceType": "customer",
    "resourcePattern": "*",
    "key": "customer:read:region",
    "logic": {
      "some": [
        {"var": "resource.tags.regions"},
        {"in": [{"var": ""}, {"var": "subject.tags.regions"}]}
      ]
    }
  }'
```

### Pattern 6: Sensitivity Levels

Access based on clearance:

```bash theme={null}
# Permission: Read documents at or below your clearance
curl -X POST 'https://api.example.com/permissions' \
  -d '{
    "scopeId": "scope_org",
    "action": "read",
    "resourceType": "document",
    "resourcePattern": "*",
    "key": "document:read:clearance",
    "logic": {
      ">=": [
        {"var": "subject.meta.clearanceLevel"},
        {"var": "resource.meta.requiredClearance"}
      ]
    }
  }'
```

## Combining Conditions

### Owner OR Department

```json theme={null}
{
  "or": [
    {"==": [{"var": "resource.createdBy"}, {"var": "subject.id"}]},
    {
      "some": [
        {"var": "resource.tags.departments"},
        {"in": [{"var": ""}, {"var": "subject.tags.departments"}]}
      ]
    }
  ]
}
```

### Department AND Clearance

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

### Owner OR Manager OR Admin

```json theme={null}
{
  "or": [
    {"==": [{"var": "resource.createdBy"}, {"var": "subject.id"}]},
    {"in": [{"var": "resource.createdBy"}, {"var": "subject.meta.directReports"}]},
    {"var": "subject.meta.isAdmin"}
  ]
}
```

## Implementation Patterns

### Pre-Filtering Queries

For list views, pre-filter at the database level:

```typescript theme={null}
async function getAccessibleDocuments(userId: string, scopeId: string) {
  // Get user's effective permissions
  const permissions = await bedrock.listEffectivePermissions(userId, scopeId);

  // Extract conditions from permissions
  const conditions = extractConditions(permissions, "document", "read");

  // Build database query from conditions
  const query = buildQueryFromConditions(conditions, userId);

  return db.documents.find(query);
}
```

### Post-Filtering Results

For complex conditions, filter after fetching:

```typescript theme={null}
async function filterAccessibleDocuments(
  documents: Document[],
  userId: string,
  scopeId: string
) {
  const accessible = [];

  for (const doc of documents) {
    const decision = await bedrock.evaluate({
      actor: { subjectId: userId, subjectType: "user" },
      scopeId,
      action: "read",
      resource: { resourceId: doc.bedrockResourceId }
    });

    if (decision.allowed) {
      accessible.push(doc);
    }
  }

  return accessible;
}
```

### Checking Many Resources

There is no bulk evaluate API—call `evaluate` per resource, in parallel:

```typescript theme={null}
async function checkAccessMany(
  userId: string,
  scopeId: string,
  resourceIds: string[]
) {
  const decisions = await Promise.all(
    resourceIds.map((resourceId) =>
      bedrock.evaluate({
        actor: { subjectId: userId, subjectType: "user" },
        scopeId,
        action: "read",
        resource: { resourceId },
      })
    )
  );

  return resourceIds.filter((_, i) => decisions[i].allowed);
}
```

## Performance Considerations

<AccordionGroup>
  <Accordion title="Cache permission evaluations">
    Cache decisions for the same user/resource/action combinations.
  </Accordion>

  <Accordion title="Pre-filter when possible">
    Convert simple conditions to database queries rather than post-filtering.
  </Accordion>

  <Accordion title="Evaluate in parallel, and cache">
    There is no bulk evaluate API; issue per-resource `evaluate` calls in parallel and cache decisions for repeated user/resource/action combinations.
  </Accordion>

  <Accordion title="Denormalize for read performance">
    Store computed access lists on resources for fast filtering.
  </Accordion>

  <Accordion title="Index tag assignments">
    Ensure tag assignments are indexed for fast lookups.
  </Accordion>
</AccordionGroup>

## Best Practices

<AccordionGroup>
  <Accordion title="Keep conditions simple">
    Complex nested conditions are hard to debug and slow to evaluate.
  </Accordion>

  <Accordion title="Use tags for flexible classification">
    Tags are more flexible than hardcoded relationships.
  </Accordion>

  <Accordion title="Test edge cases">
    Test with users who have no tags, resources with no tags, etc.
  </Accordion>

  <Accordion title="Document your access model">
    Make it clear to your team how row-level access works.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Conditional Permissions" icon="code" href="/guides/conditional-permissions">
    More JSON Logic patterns
  </Card>

  <Card title="Tag-Based Access" icon="tags" href="/tags/tag-based-access">
    Deep dive into tag-based conditions
  </Card>
</CardGroup>
