# Pinned Organization Mode

Single-tenant mode has been removed. If you want a one-org deployment without org switching overhead, use `features.pinnedOrganizationId` instead.

Pinned organization mode keeps Better Auth's organization plugin, organization membership roles, and org-scoped firewalls enabled. The only difference is that Quickback hard-codes one active organization id at runtime and hides the switching UI.

## Enabling Pinned Organization Mode

Set a real organization id in your config:

```typescript
import { defineConfig, defineRuntime, defineDatabase, defineAuth } from "@quickback/compiler";

export default defineConfig({
  name: "my-site",
  template: "hono",
  features: {
    pinnedOrganizationId: "org_123",
  },
  providers: {
    runtime: defineRuntime("cloudflare"),
    database: defineDatabase("cloudflare-d1"),
    auth: defineAuth("better-auth"),
  },
});
```

> **The organization must exist**
>
> `pinnedOrganizationId` is the Better Auth organization id the compiled Worker hard-codes. The compiler does not insert that row.
>
> A **browser deploy** from Quickback Start does: the broker writes `organization.id` as this exact string, seeds `user.role = "sysadmin"`, and seats that user as `owner` of the org. `org_default` is a valid id — use it when you do not have a pre-existing org.
>
> A **CLI deploy** (`quickback deploy`) does not seed AUTH_DB. Create the organization yourself (or deploy once through Start) so the pin points at a real row.


## Removed Single-Tenant Settings

These older settings now fail the compile with a migration error:

- `features.organizations: false`
- `providers.database.config.organizations: false`
- `providers.auth.config.plugins.organization = false`

Replace them with `features.pinnedOrganizationId`.

## What Changes

| Aspect | Default Org Mode | Pinned Organization Mode |
|--------|------------------|--------------------------|
| Organization plugin | Enabled | Enabled |
| Role source | Org membership table | Org membership table in the pinned org |
| Firewall scopes | `owner`, `organization`, `team`, `exception` | Same |
| `ctx.activeOrgId` | From session / JWT / API key context | Always the configured org id |
| CMS org selector | Visible when caller can switch orgs | Hidden |
| CMS org gate | Requires an active org selection | Bypassed; pinned org is injected automatically |

## Access and Roles

Pinned organization mode does **not** bring back the old `user.role` mirror on `ctx.roles`.

- Use `roles: ["owner" | "admin" | "member"]` for data-plane access based on membership in the pinned org.
- Use `userRole: [...]` for platform-wide Better Auth roles stored on `user.role` (`appmanager`, `sysadmin`, etc.).

```typescript
// quickback/features/posts/actions/publish.ts
import { z } from "zod";
import { defineAction } from "../.quickback/define-action";

export default defineAction({
  description: "Publish a post, for either a pinned-org admin or a platform app manager.",
  input: z.object({}),
  access: {
    or: [
      { roles: ["admin"] },           // admin in the pinned org
      { userRole: ["appmanager"] },   // platform control-plane role
    ],
  },
  execute: async () => ({ ok: true }),
});
```

## Firewalls and Resource Scopes

Organization-scoped resources keep working in pinned mode. `q.scope("organization")`, `q.scope("team")`, and any firewall predicate that references `ctx.activeOrgId` still compile and run normally — they just see the pinned organization id instead of a user-selected one.

Owner-scoped and public resources are unchanged.

## CMS Behavior

When `features.pinnedOrganizationId` is set:

- the CMS hides the org switcher
- the CMS skips the org-selection gate
- API calls and realtime connections use the pinned org automatically
- role checks still come from the caller's membership in that org

This is the intended replacement for the old single-tenant CMS flow.

## Migration from Single-Tenant Mode

1. Remove `features.organizations: false`.
2. Add `features.pinnedOrganizationId: "<org-id>"`. On a Start deploy, that string is the org the broker inserts — `"org_default"` is fine. On a CLI deploy, it must already exist in AUTH_DB.
3. Remove any assumptions that `ctx.roles` mirrors `user.role`.
4. If you need platform-wide roles, keep those checks on `userRole`, not `roles`.

## Consuming the API

Pinned organization mode still produces the same JSON API shape. Pair it with any frontend framework:

```typescript
const posts = await fetch("https://api.mysite.com/api/v1/posts").then((r) => r.json());

const res = await fetch("https://api.mysite.com/api/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ title: "New Post", content: "..." }),
});
```
