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

# Principal-scoped storage (Slack team installs)

> How OpenSRE gives each Slack user laptop-like session context while sharing org integrations on a multi-tenant gateway.

# Principal-scoped storage (Slack team installs)

For a **Slack user in a team workspace**, OpenSRE resolves one **principal**
(the organization this deployment serves) and one **actor** (the Slack
user). Team integrations and billing follow the principal. Conversation
sessions follow the actor — the same private context a laptop CLI user keeps
under `~/.opensre`.

Local CLI and Telegram stay on the flat host home when no org scope is bound.

## Who owns the turn

| Context        | Principal                           | Actor         |
| -------------- | ----------------------------------- | ------------- |
| Slack          | Organization from `ORGANIZATION_ID` | Slack user id |
| Other surfaces | Unchanged (not part of this change) | —             |

With no organization configured, or a team outside `OPENSRE_SILO_TEAM_IDS`, the
Slack turn is **refused** rather than billed to the wrong owner.

## On-disk layout

A laptop user keeps everything in `~/.opensre`. A Slack user gets the same
private conversation context, filed under their organization.

Deployed, the organization root is the mounted S3 Files volume named by
`OPENSRE_CONTEXT_ROOT` (`/workspace/memories`). The infrastructure chroots that
mount to one organization through a per-org access point, so OpenSRE adds no
org segment of its own:

```text theme={null}
/workspace/memories/                     # = organizations/<organization_id>/
  integrations.json                      # shared by every member of the org
  users/<slack_user_id>/                 # that user's own context
    sessions/<session_id>.jsonl
    memory/
```

Without the mount, a bound org principal nests under the host home so several
organizations can be exercised on one machine:

```text theme={null}
~/.opensre/
  gateway/…                              # host: pidfile, logs
  integrations.json                      # laptop / unbound: unchanged
  sessions/
  memory/
  orgs/<clerk_org_id>/
    integrations.json
    users/<slack_user_id>/
      sessions/…
      memory/
```

Integrations sit at the org root because credentials belong to the team: whoever
connected Datadog, every member investigates with it. Sessions and memory sit
under the user because conversation history is personal, the same way it is on a
laptop.

With no scope bound — a terminal run — every path resolves to the flat
`~/.opensre` layout, unchanged.

## What changes for Slack

1. Resolve `StorageScope` (`principal` + `actor`) at the start of each Slack turn.
2. Bind the scope for the turn (`bound_storage_scope`).
3. Resolve paths: org home for integrations, member home for sessions.
4. Look up / create session bindings with
   `(platform, chat_id, principal_id, actor_id)`.
5. Consume credits against `principal.id` (the org).

Alice and Bob in the same Slack thread get different session files and different
binding rows. They share the org's integrations store.

## Other surfaces

`principal` and `actor` are optional on the binding store and resolver. Callers
that omit them — Telegram, and anything else on main's path — key bindings by
empty principal/actor ids, which is exactly the behavior they had before.

## Which organization a deployment serves

There is no install catalog. The team → organization mapping is control-plane
data the webapp already owns (`organizations.workspace_provider` and the Slack
team id on the `organizations` row), so OpenSRE does not keep a second copy that
could drift.

A deployment is told which organization it serves:

```bash theme={null}
export ORGANIZATION_ID=org_...
export OPENSRE_SILO_TEAM_IDS=T0123        # recommended in production
```

With `OPENSRE_SILO_TEAM_IDS` set, only those teams are served and every other
workspace is refused. Unset, any workspace that installed the app is served from
the configured organization — convenient for dogfood, and logged as a warning
because it means an uninvited workspace would inherit that organization's
credentials.

**Consequence:** one process serves one organization. A gateway fronting several
workspaces would need the team → organization lookup back, reading the webapp
rather than a local catalog.

## Upgrading an existing deployment

Session bindings moved out of the host SQLite database into a per-organization
`bindings.json` beside that organization's context. SQLite is gone from the live
path: the context root is an NFS-backed mount, where its advisory locking is
unreliable, while a write-temp-then-rename is atomic.

Opening the JSON file for the first time adopts rows from any old SQLite index —
including one written before scoping existed, which has neither `principal_id`
nor `actor_id`; those adopt as unscoped rows and keep working for Telegram and
the CLI. A deployment whose context moved onto a mounted volume also adopts from
the host database.

Slack members still get a **new session on their first turn after upgrade**: an
adopted row carries an empty actor id, so it no longer matches a member-scoped
lookup. The transcript survives and remains readable.

Session transcripts and `integrations.json` are not copied automatically. To
keep continuity on a silo, copy them once:

```bash theme={null}
mkdir -p ~/.opensre/orgs/$ORGANIZATION_ID/users
cp ~/.opensre/integrations.json ~/.opensre/orgs/$ORGANIZATION_ID/
```

Telegram and other surfaces keep matching their migrated empty-actor rows.

## Related env vars

| Env                     | Purpose                                                                                                                                                    |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ORGANIZATION_ID`       | The organization this deployment serves. Required for Slack turns, and required to use `OPENSRE_CONTEXT_ROOT`                                              |
| `OPENSRE_CONTEXT_ROOT`  | Mounted, org-scoped volume holding this organization's context (`/workspace/memories` in the Slack service). Requires `ORGANIZATION_ID` to say who owns it |
| `OPENSRE_SILO_TEAM_IDS` | Comma-separated Slack team ids this deployment serves. Unset, any workspace is served (with a warning); set, every other workspace is refused              |

## Non-goals (this change)

* CLI emulation of a Slack member
* Per-user integration credentials or LLM auth
* Nesting `opensre.json`, investigations, or REPL history under member homes
* Changing local CLI individual home layout
* A database of any kind: bindings are a JSON file
* A team → organization catalog, so one process serves one organization
