> ## 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 Clerk organization that owns the install) 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 **team install** | Organization (`clerk_org_id` from install, else silo `OPENSRE_ORGANIZATION_ID`) | Slack user id |
| Other surfaces         | Unchanged (not part of this change)                                             | —             |

If the install catalog is unreadable, or there is neither an install row nor a
silo org id, 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: state.db, pid, 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.

## Registering a workspace

Nothing in this change writes `slack_installs` rows: there is no OAuth or
control-plane hook yet. Until one exists, give a workspace an owner in one of
two ways.

Set the silo organization, which every workspace without a row falls back to:

```bash theme={null}
export OPENSRE_ORGANIZATION_ID=org_...
```

Or insert the row directly, which is what a real multi-workspace deployment
needs:

```python theme={null}
from gateway.slack.installs import upsert_slack_install

upsert_slack_install(team_id="T0123", clerk_org_id="org_...")
```

With neither, a Slack turn from that workspace is refused.

## Upgrading an existing deployment

Session bindings moved out of the host `state.db` into a per-organization
`bindings.db` beside that organization's context. Opening the new database once
adopts any rows from the old one — including databases written before scoping
existed, which have neither `principal_id` nor `actor_id`; those adopt as
unscoped rows and keep working for Telegram and the CLI. A silo 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/$OPENSRE_ORGANIZATION_ID/users
cp ~/.opensre/integrations.json ~/.opensre/orgs/$OPENSRE_ORGANIZATION_ID/
```

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

## Related env vars

| Env                       | Purpose                                                                                                                                                                        |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OPENSRE_ORGANIZATION_ID` | Silo org principal when no `slack_installs` row exists yet (dogfood / cutover)                                                                                                 |
| `OPENSRE_CONTEXT_ROOT`    | Mounted, org-scoped volume holding this organization's context (`/workspace/memories` in the Slack service). Requires `OPENSRE_ORGANIZATION_ID` to say who owns it             |
| `OPENSRE_SILO_TEAM_IDS`   | Comma-separated Slack team ids allowed to fall back to the silo organization. Unset, any team without an install falls back (with a warning); set, every other team 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
* Postgres session bindings
* OAuth writer for `slack_installs`
