# Single-Org Deployment Mode Status: implemented first pass Owner: self-host / enterprise Related: `packaging/helm/openwork-ee`, `ee/apps/den-api/src/auth.ts`, `ee/apps/den-api/src/orgs.ts`, `ee/apps/den-web/app/(den)` Implementation note: this document started as the plan for the subagent split. The first pass now implements the default `single_org` mode, singleton org bootstrap and membership enforcement, route guards, Den Web runtime config, UI flow changes, Helm/Docker env wiring, validation proof, and SSO-only auth when the singleton organization has SSO configured. SCIM-specific singleton JIT behavior and production bootstrap-token UX remain follow-up work. ## Goal Make self-hosted OpenWork EE deployments behave like one customer-owned workspace by default, while keeping hosted OpenWork Cloud multi-org. The internal model should still use organizations, members, roles, SSO, SCIM, workers, plugins, API keys, and MCP resources. The product experience should stop asking self-hosted users to create or choose organizations. A Helm install should have one deployment organization, one ownership model, and one clear path from first admin setup to SSO/SCIM-managed usage. ## Deployment Modes Introduce an explicit org mode: ```text DEN_ORG_MODE=single_org | multi_org ``` Recommended defaults: - Helm chart: `single_org` - Local development: `single_org` unless a developer opts into multi-org - Hosted OpenWork Cloud: `multi_org` Suggested Helm values: ```yaml config: tenancy: mode: single_org singleOrgName: OpenWork singleOrgSlug: default ownerEmails: "" allowPublicSignup: "false" requireEmailVerification: "false" ``` `ownerEmails` should reuse or replace the current `config.public.bootstrapAdminEmails` concept. The important distinction is that bootstrap admin currently seeds internal admin access, while single-org owner bootstrap controls the deployment organization owner. Packaging implementation note: Helm now exposes these values to both Den API and Den Web as `DEN_ORG_MODE`, `DEN_SINGLE_ORG_NAME`, `DEN_SINGLE_ORG_SLUG`, `DEN_SINGLE_ORG_OWNER_EMAILS`, `DEN_SINGLE_ORG_ALLOW_PUBLIC_SIGNUP`, and `DEN_REQUIRE_EMAIL_VERIFICATION`. Blank or unset `DEN_ORG_MODE` resolves to `single_org`; hosted/cloud deployments should set `DEN_ORG_MODE=multi_org` explicitly. Den web also needs the mode exposed through runtime config. Today `ee/apps/den-web/app/api/runtime-config/route.ts` exposes app-connect and auth callback URLs only, so UI workers should add deployment-mode data there instead of duplicating env parsing in many components. ## Product Principles Single-org mode is a deployment policy, not a separate product data model. - Keep org-scoped resources internally. - Guarantee one deployment organization. - Automatically select the deployment organization for every session. - Block creation of additional organizations. - Hide or remove organization switching and organization creation UI. - Treat SSO/SCIM as the normal production path. - Keep a deliberate break-glass owner path for first setup and recovery. ## Target Behavior ### Fresh Install The server can create the singleton organization idempotently from env/config. If no owner exists: 1. If owner emails are configured, only those emails can claim owner. 2. If no owner emails are configured, dev/local may allow first user owner. 3. Production Helm should strongly prefer explicit owner emails or a setup token. The first owner lands on a setup checklist: - confirm organization name - configure SAML/OIDC SSO - configure allowed domains and SSO requirement - optionally configure SCIM - invite or provision users - install desktop / connect workers ### Normal User Sign-In Once the singleton organization exists: - If SSO is configured on the singleton organization, the root auth experience shows only "Continue with SSO". - Sign-in/sign-up pages redirect to that root SSO-only experience. - Raw Better Auth email/password sign-in and sign-up requests are rejected with `single_org_sso_required`. - SSO login JIT-provisions the user into the singleton org. - SCIM-created users are attached to the singleton org. - Email verification is disabled by default in single-org Helm mode. ### Organization APIs In `single_org` mode: - `POST /v1/org` should return a clear single-org error or idempotently return the singleton organization if the caller is already a member. - `/api/auth/organization/create` must not bypass the Den route policy. - Active-org switch routes should only accept the singleton org, or become a no-op that returns it. - `/v1/me/orgs` should return exactly one org for normal users. - MCP OAuth should never force the user through a select-organization screen just because the session was created before org hydration. ## Current Code Map Backend: - `ee/apps/den-api/src/env.ts`: parse deployment mode. - `ee/apps/den-api/src/orgs.ts`: org creation, member creation, org list, org context, ownership transfer, dynamic role seeding. - `ee/apps/den-api/src/active-organization.ts`: initial active org for new sessions. - `ee/apps/den-api/src/middleware/user-organizations.ts`: resolves visible orgs and hydrates session active org. - `ee/apps/den-api/src/middleware/organization-context.ts`: loads active org context and validates membership/API key scope. - `ee/apps/den-api/src/auth.ts`: Better Auth config, email verification, org plugin, SSO/SCIM plugin behavior, MCP OAuth org claims. - `ee/apps/den-api/src/routes/org/core.ts`: org create/read/update/invite. - `ee/apps/den-api/src/routes/me/index.ts`: org directory and active-org endpoints. - `ee/apps/den-api/src/routes/auth/index.ts`: raw Better Auth handler surface. Frontend: - `ee/apps/den-web/app/(den)/_providers/den-flow-provider.tsx`: email auth, verification, SSO redirects, landing route. - `ee/apps/den-web/app/(den)/_components/auth-panel.tsx`: sign-in/sign-up UI. - `ee/apps/den-web/app/(den)/_components/organization-screen.tsx`: current create/switch organization screen. - `ee/apps/den-web/app/(den)/dashboard/_providers/org-dashboard-provider.tsx`: redirects to `/organization` when no org exists. - `ee/apps/den-web/app/sso/[orgSlug]/page.tsx`: org-slug SSO entrypoint. Packaging and docs: - `packaging/helm/openwork-ee/values.yaml` - `packaging/helm/openwork-ee/templates/configmap.yaml` - `packaging/helm/openwork-ee/README.md` - `packages/docs/start-here/self-host.mdx` ## Implementation Work Packages Each package should be handled by a focused subagent or worker in a disjoint write area, then reviewed and integrated by the orchestrator. ### 1. Config And Helm Surface Owner: Helm/config worker Files: - `ee/apps/den-api/src/env.ts` - `packaging/helm/openwork-ee/values.yaml` - `packaging/helm/openwork-ee/templates/configmap.yaml` - `packaging/helm/openwork-ee/README.md` - `packages/docs/start-here/self-host.mdx` Tasks: 1. Add `DEN_ORG_MODE` parsing with default `single_org` for Helm values and a clearly documented `multi_org` override for hosted cloud. 2. Add singleton org name/slug/owner-email config. 3. Pass `DEN_ORG_MODE` and singleton settings through Docker/local dev scripts where relevant, including `packaging/docker/docker-compose.den-dev.yml` and `packaging/docker/den-dev-up.sh`. 4. Document the ownership split between self-host single-org and hosted cloud multi-org. 5. Keep existing password breach screening behavior: self-host still avoids external breach checks by default. Validation: - render/lint through CI if local `helm` is unavailable - targeted env parsing tests - doc review against Helm values ### 2. Singleton Organization Backend Owner: backend tenancy worker Files: - `ee/apps/den-api/src/orgs.ts` - `ee/apps/den-api/src/active-organization.ts` - `ee/apps/den-api/src/middleware/user-organizations.ts` - `ee/apps/den-api/src/middleware/organization-context.ts` - new or existing tests under `ee/apps/den-api/test` Tasks: 1. Add idempotent singleton org resolver. 2. Use fixed unique slug and duplicate-key re-query to handle concurrent first requests. 3. Create the first eligible owner, then attach later users as members. 4. Ensure default desktop policy and dynamic roles are seeded. 5. Ensure session creation and org middleware always resolve the singleton for eligible users. Validation: - first eligible user becomes owner - second eligible user becomes member - concurrent singleton creation does not create two orgs - stale session active org is repaired - `/v1/me/orgs` returns one active org ### 3. Auth, Owner Bootstrap, And SSO Policy Owner: auth worker Files: - `ee/apps/den-api/src/auth.ts` - `ee/apps/den-api/src/enterprise-auth-requirement.ts` - `ee/apps/den-api/src/sso-jit.ts` - `ee/apps/den-api/src/routes/org/sso.ts` - `ee/apps/den-api/src/routes/auth/scim.ts` - new or existing auth/SSO/SCIM tests Tasks: 1. Disable email verification by default in single-org mode. 2. Enforce owner bootstrap policy before creating users when possible. 3. Route SSO-required domains to SSO before email/password sign-in or signup. 4. Ensure SSO JIT and SCIM attach users to the singleton org. 5. Preserve owner role immutability and protect against SCIM removing the last owner. Validation: - configured owner email can bootstrap owner - unconfigured email cannot steal owner in production policy - SSO-required email/password users are redirected/rejected correctly - SSO JIT creates member membership in singleton org - SCIM deactivation does not remove the only owner ### 4. API Route Guards Owner: route behavior worker Files: - `ee/apps/den-api/src/routes/org/core.ts` - `ee/apps/den-api/src/routes/me/index.ts` - `ee/apps/den-api/src/routes/auth/index.ts` - route tests under `ee/apps/den-api/test` Tasks: 1. Block or reshape `POST /v1/org` in single-org mode. 2. Block raw Better Auth organization creation/switch bypasses. 3. Make active-org switching idempotent for the singleton. 4. Preserve multi-org behavior when `DEN_ORG_MODE=multi_org`. Validation: - `/v1/org` cannot create a second org - `/api/auth/organization/create` cannot create a second org - `/api/auth/organization/set-active` cannot switch to a non-singleton org - multi-org tests keep passing when mode is `multi_org` ### 5. Den Web Single-Org UX Owner: frontend worker Files: - `ee/apps/den-web/app/(den)/_providers/den-flow-provider.tsx` - `ee/apps/den-web/app/(den)/_components/auth-panel.tsx` - `ee/apps/den-web/app/(den)/_components/organization-screen.tsx` - `ee/apps/den-web/app/(den)/dashboard/_providers/org-dashboard-provider.tsx` - `ee/apps/den-web/app/(den)/dashboard/_components/org-dashboard-shell.tsx` - `ee/apps/den-web/app/api/runtime-config/route.ts` Tasks: 1. Expose deployment mode to Den web. 2. Hide sign-up or org creation flows when policy requires SSO/setup. 3. Replace "Name your team" with setup/status behavior in single-org mode. 4. Redirect `/organization` to dashboard when singleton org exists. 5. Hide org switcher/create affordances in single-org mode. 6. Add a non-slug SSO affordance for singleton deployments, or build the direct SSO URL from runtime config. 7. Keep hosted cloud multi-org UX unchanged. Validation: - first admin can reach setup flow - normal SSO user lands on dashboard - no organization creation prompt appears in single-org mode - org switcher/create controls are absent - multi-org mode still shows create/switch flows ### 6. End-To-End Validation And Test Evidence Owner: validation worker plus orchestrator Commands: ```bash pnpm dev:web-local ``` Expected local services: - Den API: `http://localhost:8788` when using `pnpm dev:web-local` - Den web: `http://localhost:3005` - Worker proxy: `http://localhost:8789` - Inference: `http://localhost:8791` - MySQL: local Docker compose service Note: the split script `pnpm dev:den:api` defaults Den API to `8790`; do not confuse that with the all-in-one `pnpm dev:web-local` path. Baseline check on 2026-07-05: `pnpm dev:web-local` did not reach app startup. Pnpm detected that the pulled lockfile and existing `node_modules` layout were out of sync, attempted an install, warned that registry metadata fetch failed, and prompted to remove and reinstall module directories. The orchestrator declined the destructive install prompt during planning. Before local runtime validation, run a deliberate dependency repair step such as `pnpm install --frozen-lockfile` with network access approved. Scenarios: 1. Fresh DB single-org owner bootstrap. 2. Second direct signup joins singleton as member or is blocked according to policy. 3. SSO-required email/password attempt is redirected or rejected before creating an unintended account. 4. Dashboard loads without `/organization` org-creation detour. 5. Org settings/SSO/member pages operate against singleton org. 6. Multi-org mode still allows org creation/switching. Baseline check on 2026-07-05 after dependency repair: 1. `pnpm install --frozen-lockfile` completed successfully with pnpm 11.7.0. 2. First `pnpm dev:web-local` runtime attempt started MySQL and schema sync but `den-api` failed because `@openwork/install-config` had no `dist/index.js`. 3. `pnpm --filter @openwork/install-config build` fixed the missing package artifact. 4. Restarted `pnpm dev:web-local`; all services came up: - Den API: `http://127.0.0.1:8788` - Den web: `http://127.0.0.1:3005` - Inference: `http://127.0.0.1:8791` - Worker proxy: `http://127.0.0.1:8789` 5. Health/readiness probes passed: - `curl -sS http://127.0.0.1:8788/health` - `curl -sS -i http://127.0.0.1:8788/ready` - `curl -sS http://127.0.0.1:8791/health` - `curl -sS -i http://127.0.0.1:3005/api/health` - `curl -sS -i http://127.0.0.1:3005/api/ready` Helm render checks after config work: ```bash helm template openwork-ee ./packaging/helm/openwork-ee --set image.tag=test | rg "DEN_ORG_MODE|DEN_SINGLE" helm template openwork-ee ./packaging/helm/openwork-ee --set config.tenancy.mode=multi_org --set image.tag=test | rg "DEN_ORG_MODE" ``` Local `helm` is not installed in the current Codex environment, so use CI or an environment with Helm for this until local tooling is available. Current test-evidence follow-up: - Create or update an E2E test proving the single-org first-run/admin setup path. - Add a second E2E test proving multi-org cloud behavior is unchanged, or run the canonical test if the UI changes are expected to be inert. Validation update on 2026-07-05: 1. `pnpm --filter @openwork-ee/den-api exec bun test test/single-org-mode.test.ts test/single-org-route-guards.test.ts` passed 8 tests. 2. `pnpm --filter @openwork-ee/den-api exec tsc --noEmit` passed. 3. `pnpm --filter @openwork-ee/den-web exec tsc --noEmit` passed. 4. `git diff --check` passed. 5. `bash -n packaging/docker/den-dev-up.sh` passed. 6. `docker compose -f packaging/docker/docker-compose.den-dev.yml config | rg "DEN_ORG_MODE|DEN_SINGLE_ORG|DEN_REQUIRE_EMAIL_VERIFICATION"` showed `single_org`, singleton org settings, and disabled email verification. 7. `pnpm dev:web-local` started Den API, Den Web, inference, and Docker MySQL. Health, readiness, runtime config, signup/signin, singleton membership, and org-create rejection checks passed. 8. Historical legacy-runner record: `pnpm fraimz --flow den-single-org-mode --cdp-url http://127.0.0.1:9825` passed and produced the retained legacy report `evals/results/2026-07-05T15-25-15-884Z/fraimz.html`. 9. Native `helm version --short` failed because Helm is not installed locally. Helm validation was completed through OrbStack Docker instead: - `docker run --rm -v /Users/omar/code/openwork/packaging/helm/openwork-ee:/chart:ro alpine/helm:3.15.4 version --short` returned `v3.15.4+gfa9efb0`. - `docker run --rm -v /Users/omar/code/openwork/packaging/helm/openwork-ee:/chart:ro alpine/helm:3.15.4 lint /chart --set image.tag=test` passed with 0 chart failures. - `docker run --rm -v /Users/omar/code/openwork/packaging/helm/openwork-ee:/chart:ro alpine/helm:3.15.4 template openwork-ee /chart --set image.tag=test --show-only templates/configmap.yaml` rendered `DEN_ORG_MODE: "single_org"` and singleton settings. - `docker run --rm -v /Users/omar/code/openwork/packaging/helm/openwork-ee:/chart:ro alpine/helm:3.15.4 template openwork-ee /chart --set image.tag=test --set config.tenancy.mode=multi_org --show-only templates/configmap.yaml` rendered `DEN_ORG_MODE: "multi_org"`. ## Orchestration Plan The orchestrator is responsible for sequencing, reviewing, integrating, and validating worker output. 1. Write and maintain this plan document. 2. Use read-only explorers to audit backend, auth, UI, Helm/docs, and validation. 3. Assign implementation workers only after the target contract is stable. 4. Keep worker write scopes disjoint. 5. Review every worker diff before integration. 6. Run focused tests after each slice. 7. Run `pnpm dev:web-local` and browser-driven E2E validation after integrated UI behavior exists. 8. Update Helm README and public self-host docs before final handoff. ## Open Decisions 1. Should Helm require explicit owner emails in production, or allow first user owner with a warning? 2. Should single-org mode allow email/password member signup after SSO is configured, or make SSO mandatory except break-glass owners? 3. Should `POST /v1/org` in single-org mode return 403, 409, or the singleton org for existing members? 4. Should singleton org slug be configurable, always `default`, or derived from the release name? 5. Should hosted cloud local dev default to `multi_org`, while regular local dev follows Helm `single_org`? 6. How should existing customer DBs with multiple orgs behave when `DEN_ORG_MODE=single_org` is enabled: fail fast, choose a configured slug, or migrate one org? 7. Should self-host docs call Den web "OpenWork Cloud" after Helm defaults to single-org mode, or rename that surface in docs to "OpenWork web" / "OpenWork admin" for private deployments? ## Documentation Updates Needed Self-host and packaging docs need to stop describing the Helm path as if users must understand multi-org creation. - `packages/docs/start-here/self-host.mdx`: distinguish single-org private deployments from hosted OpenWork Cloud. The current "Cloud-style accounts, teams, and organization management" wording conflicts with a single-org Helm default. - `packages/docs/start-here/self-host.mdx`: fix the "Minimal Working Stack" section, which says "two services" but lists three. - `packages/docs/cloud/security-and-operations.mdx`: clarify that private single-org deployments still use org-scoped RBAC internally, while hosted cloud remains multi-org. - `packaging/docker/README.md`: avoid calling every local/private web surface "OpenWork Cloud web app" after single-org mode lands. ## Proposed First Milestone Milestone 1 should prove the backend invariant without changing the whole UI: 1. Add env and Helm config. 2. Add singleton org resolver. 3. Auto-attach users to singleton org in single-org mode. 4. Block extra org creation. 5. Disable email verification in single-org mode. 6. Add focused den-api tests. After that, Milestone 2 can reshape Den web flows and end-to-end validation.