19 KiB
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:
DEN_ORG_MODE=single_org | multi_org
Recommended defaults:
- Helm chart:
single_org - Local development:
single_orgunless a developer opts into multi-org - Hosted OpenWork Cloud:
multi_org
Suggested Helm values:
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:
- If owner emails are configured, only those emails can claim owner.
- If no owner emails are configured, dev/local may allow first user owner.
- 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/orgshould return a clear single-org error or idempotently return the singleton organization if the caller is already a member./api/auth/organization/createmust 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/orgsshould 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/organizationwhen no org exists.ee/apps/den-web/app/sso/[orgSlug]/page.tsx: org-slug SSO entrypoint.
Packaging and docs:
packaging/helm/openwork-ee/values.yamlpackaging/helm/openwork-ee/templates/configmap.yamlpackaging/helm/openwork-ee/README.mdpackages/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.tspackaging/helm/openwork-ee/values.yamlpackaging/helm/openwork-ee/templates/configmap.yamlpackaging/helm/openwork-ee/README.mdpackages/docs/start-here/self-host.mdx
Tasks:
- Add
DEN_ORG_MODEparsing with defaultsingle_orgfor Helm values and a clearly documentedmulti_orgoverride for hosted cloud. - Add singleton org name/slug/owner-email config.
- Pass
DEN_ORG_MODEand singleton settings through Docker/local dev scripts where relevant, includingpackaging/docker/docker-compose.den-dev.ymlandpackaging/docker/den-dev-up.sh. - Document the ownership split between self-host single-org and hosted cloud multi-org.
- Keep existing password breach screening behavior: self-host still avoids external breach checks by default.
Validation:
- render/lint through CI if local
helmis 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.tsee/apps/den-api/src/active-organization.tsee/apps/den-api/src/middleware/user-organizations.tsee/apps/den-api/src/middleware/organization-context.ts- new or existing tests under
ee/apps/den-api/test
Tasks:
- Add idempotent singleton org resolver.
- Use fixed unique slug and duplicate-key re-query to handle concurrent first requests.
- Create the first eligible owner, then attach later users as members.
- Ensure default desktop policy and dynamic roles are seeded.
- 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/orgsreturns one active org
3. Auth, Owner Bootstrap, And SSO Policy
Owner: auth worker
Files:
ee/apps/den-api/src/auth.tsee/apps/den-api/src/enterprise-auth-requirement.tsee/apps/den-api/src/sso-jit.tsee/apps/den-api/src/routes/org/sso.tsee/apps/den-api/src/routes/auth/scim.ts- new or existing auth/SSO/SCIM tests
Tasks:
- Disable email verification by default in single-org mode.
- Enforce owner bootstrap policy before creating users when possible.
- Route SSO-required domains to SSO before email/password sign-in or signup.
- Ensure SSO JIT and SCIM attach users to the singleton org.
- 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.tsee/apps/den-api/src/routes/me/index.tsee/apps/den-api/src/routes/auth/index.ts- route tests under
ee/apps/den-api/test
Tasks:
- Block or reshape
POST /v1/orgin single-org mode. - Block raw Better Auth organization creation/switch bypasses.
- Make active-org switching idempotent for the singleton.
- Preserve multi-org behavior when
DEN_ORG_MODE=multi_org.
Validation:
/v1/orgcannot create a second org/api/auth/organization/createcannot create a second org/api/auth/organization/set-activecannot 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.tsxee/apps/den-web/app/(den)/_components/auth-panel.tsxee/apps/den-web/app/(den)/_components/organization-screen.tsxee/apps/den-web/app/(den)/dashboard/_providers/org-dashboard-provider.tsxee/apps/den-web/app/(den)/dashboard/_components/org-dashboard-shell.tsxee/apps/den-web/app/api/runtime-config/route.ts
Tasks:
- Expose deployment mode to Den web.
- Hide sign-up or org creation flows when policy requires SSO/setup.
- Replace "Name your team" with setup/status behavior in single-org mode.
- Redirect
/organizationto dashboard when singleton org exists. - Hide org switcher/create affordances in single-org mode.
- Add a non-slug SSO affordance for singleton deployments, or build the direct SSO URL from runtime config.
- 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:
pnpm dev:web-local
Expected local services:
- Den API:
http://localhost:8788when usingpnpm 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:
- Fresh DB single-org owner bootstrap.
- Second direct signup joins singleton as member or is blocked according to policy.
- SSO-required email/password attempt is redirected or rejected before creating an unintended account.
- Dashboard loads without
/organizationorg-creation detour. - Org settings/SSO/member pages operate against singleton org.
- Multi-org mode still allows org creation/switching.
Baseline check on 2026-07-05 after dependency repair:
pnpm install --frozen-lockfilecompleted successfully with pnpm 11.7.0.- First
pnpm dev:web-localruntime attempt started MySQL and schema sync butden-apifailed because@openwork/install-confighad nodist/index.js. pnpm --filter @openwork/install-config buildfixed the missing package artifact.- 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
- Den API:
- Health/readiness probes passed:
curl -sS http://127.0.0.1:8788/healthcurl -sS -i http://127.0.0.1:8788/readycurl -sS http://127.0.0.1:8791/healthcurl -sS -i http://127.0.0.1:3005/api/healthcurl -sS -i http://127.0.0.1:3005/api/ready
Helm render checks after config work:
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:
pnpm --filter @openwork-ee/den-api exec bun test test/single-org-mode.test.ts test/single-org-route-guards.test.tspassed 8 tests.pnpm --filter @openwork-ee/den-api exec tsc --noEmitpassed.pnpm --filter @openwork-ee/den-web exec tsc --noEmitpassed.git diff --checkpassed.bash -n packaging/docker/den-dev-up.shpassed.docker compose -f packaging/docker/docker-compose.den-dev.yml config | rg "DEN_ORG_MODE|DEN_SINGLE_ORG|DEN_REQUIRE_EMAIL_VERIFICATION"showedsingle_org, singleton org settings, and disabled email verification.pnpm dev:web-localstarted Den API, Den Web, inference, and Docker MySQL. Health, readiness, runtime config, signup/signin, singleton membership, and org-create rejection checks passed.- Historical legacy-runner record:
pnpm fraimz --flow den-single-org-mode --cdp-url http://127.0.0.1:9825passed and produced the retained legacy reportevals/results/2026-07-05T15-25-15-884Z/fraimz.html. - Native
helm version --shortfailed 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 --shortreturnedv3.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=testpassed 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.yamlrenderedDEN_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.yamlrenderedDEN_ORG_MODE: "multi_org".
Orchestration Plan
The orchestrator is responsible for sequencing, reviewing, integrating, and validating worker output.
- Write and maintain this plan document.
- Use read-only explorers to audit backend, auth, UI, Helm/docs, and validation.
- Assign implementation workers only after the target contract is stable.
- Keep worker write scopes disjoint.
- Review every worker diff before integration.
- Run focused tests after each slice.
- Run
pnpm dev:web-localand browser-driven E2E validation after integrated UI behavior exists. - Update Helm README and public self-host docs before final handoff.
Open Decisions
- Should Helm require explicit owner emails in production, or allow first user owner with a warning?
- Should single-org mode allow email/password member signup after SSO is configured, or make SSO mandatory except break-glass owners?
- Should
POST /v1/orgin single-org mode return 403, 409, or the singleton org for existing members? - Should singleton org slug be configurable, always
default, or derived from the release name? - Should hosted cloud local dev default to
multi_org, while regular local dev follows Helmsingle_org? - How should existing customer DBs with multiple orgs behave when
DEN_ORG_MODE=single_orgis enabled: fail fast, choose a configured slug, or migrate one org? - 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:
- Add env and Helm config.
- Add singleton org resolver.
- Auto-attach users to singleton org in single-org mode.
- Block extra org creation.
- Disable email verification in single-org mode.
- Add focused den-api tests.
After that, Milestone 2 can reshape Den web flows and end-to-end validation.