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

# Security

> What is guaranteed, how it is enforced, and what is logged.

Riktra holds what a company has decided and what its people are working on. That is worth protecting, and worth being specific about.

## The guarantees

<CardGroup cols={2}>
  <Card title="Tenant isolation" icon="building-lock">
    Every exposed table has row-level security. A query from one company cannot return another's rows — on the web path and the MCP path alike.
  </Card>

  <Card title="No admin bypass on private context" icon="lock">
    `personal_context` and `focus_items` are owner-scoped. No role reads around it, and private records never enter org-scoped retrieval, logs or analytics.
  </Card>

  <Card title="AI cannot change shared truth" icon="gavel">
    Company writes go through proposals and human approval. Direct writes are private-scope only.
  </Card>

  <Card title="Versioned history" icon="clock-rotate-left">
    Shared-truth writes create a version row in the same transaction, enforced by a database trigger. Restoring creates a new version; history is never rewritten.
  </Card>
</CardGroup>

## How this is enforced rather than promised

Row-level security is the tenancy mechanism, not a layer on top of one. The consequence worth understanding: the retrieval path that assembles context for an AI does no permission filtering of its own, by design. A bug there returns *too little* context rather than someone else's.

Versioning is a trigger rather than a convention, so no write path can forget it.

Roles are checked in the database rather than in the description a model reads before calling a tool.

## Asserted in CI, not documented in a checklist

A `security_lint()` function runs against the schema and checks:

* RLS coverage on every exposed table
* role-scoped policies
* `search_path` pinning on security-definer functions
* `PUBLIC` grants on internal functions

It is asserted empty on every change. During hardening it found two live gaps — a `PUBLIC` grant that left the rate limiter callable, and default privileges exposing a table intended to be internal — which is the argument for having it.

Tenant isolation is covered by integration tests that run against a real Postgres as genuine signed-in users, not as a privileged client. A test that passes because it used the service key proves nothing.

## Authentication

Sign-in is Supabase Auth. MCP clients authenticate through OAuth 2.1 with per-user tokens; because those tokens are Supabase JWTs, row-level security applies to MCP calls automatically rather than through a parallel implementation.

The service-role key never reaches the browser or any client bundle, and CI fails the build if it appears in the repository.

## The anti-surveillance boundary

Riktra does not show managers what individuals are doing. There is no view, export or tool that aggregates one person's activity for someone else to read, and adding one would contradict the product rather than extend it.

## What is logged

MCP calls are logged with the user, organization, tool name and outcome — enough to answer "did the connection work" and "who called what". Private context never appears in a log, in analytics, or in any error payload.
