Organization Sync (SaaS IDE)

Organization management for the CFactory SaaS IDE lives in a hosted control plane (or the cfactory-admin React + shadcn UI in this monorepo). The CLI runtime (packages/opencode) is the only sync consumer. Editors inherit via cfactory serve.

ℹ️Scope

This page describes repository-defined sync contracts. Local cfactory-gateway-server is a real, multi-tenant control plane backed by Postgres (cp_* tables): users, organizations, members/invites, teams, projects, skills/commands/rules, providers, MCP servers, model policies, and a usage ledger. Keycloak is the identity provider (OIDC) and Bifrost is the inference upstream; request metering is parsed from Bifrost responses and stored per org/project/user. SSO federation and hosted billing integrations live in CFactory-Org/cloud.

Hard invariants

  1. Organization artifacts never enter a git worktree (no writes to .cfactory/, AGENTS.md, or marketplace-style project installs).
  2. Org and user content coexist (skills, commands, rules) — same basename does not clobber user content.
  3. Free-form mergeDeep of /api/config is rejected. Only allowlisted keys apply.
  4. Every org query on the gateway is scoped to a verified membership (WHERE organization_id = $org after requireOrg). A claimed x-org-id / :org is never trusted on its own.

Allowlisted /api/config

Accepted:

  • skills.urls (HTTPS indexes pulled into ~/.cache/cfactory/org/<orgId>/skills/)
  • commands.urls (HTTPS indexes pulled into ~/.cache/cfactory/org/<orgId>/commands/ and loaded as slash commands — never written into the repo). Pulled from the bundle, not merged into Config.
  • instructions (HTTPS URLs only)
  • cfactory.org (models, providers, mcp, extra skill/command/instruction URLs)

cfactory.org.models fields:

  • allow / deny / defaultModel
  • allowLocalModelOverrideByProject — map of projectId → boolean (resolved against org-bindings.json)
  • allowLocalModelOverride — optional global fallback

cfactory.org.providers fields (structured; never free-form provider merge):

  • enabled — CFly staff master switch for the org
  • allowCustom — org admin: members may add local custom providers
  • catalog — org-published provider entries (id, name, npm, baseURL, models) with no secrets

Ignored (examples): permission, mcp, agent, formatter, tui, provider, skills.paths, commands.paths.

Authenticated, project-filtered content

Org content routes require the same session and membership as /api/config. The CLI attaches credentials only to the active org's gateway namespace (/api/organizations/<orgId>/...), never to third-party HTTPS URLs an admin might also list:

  • Authorization: Bearer <console token> (from CFACTORY_CONSOLE_TOKEN)
  • x-org-id: <orgId>
  • x-cfactory-projectid: <projectId> when the directory is bound to a project

This applies to /api/config, the skill index (skills/index.json) and files (skills/<name>/SKILL.md), the command index (commands/index.json), and rule markdown (rules/<path>). Unauthenticated GETs of org content are a tenancy hole and are no longer used.

When a projectId is present, the gateway returns only:

  • visibility=organization — every project in the org,
  • visibility=selected — deployed to that project (cp_*_deployments),
  • visibility=project — owned by that project (origin_project_id).

The unbound CLI (no projectId) sees org-wide items only.

CLI entrypoints

Sync runs on Config load for TUI, cfactory run, and cfactory serve. Acceptance: org skills/rules appear without the VS Code extension installed.

Org model/provider policy is applied at runtime:

  • When allowLocalModelOverride is false for the bound project, local model / small_model / subagent_model / agent.*.model overrides are ignored and defaultModel is applied.
  • Provider catalog entries are additive; local OpenAI-compatible custom providers are stripped when staff has not enabled the feature or the admin has disabled custom adds.
  • consoleManagedProviders is populated from the org catalog.
  • The model catalog is pre-filtered with modelAllowed().

Project bind

~/.config/cfactory/org-bindings.json maps directory or git remote → organizationId + projectId. The CLI sets CFACTORY_PROJECT_ID for gateway usage headers.

Model policy

The live policy is cp_model_policies (admin toggles), briefly cached in Redis. The gateway returns 403 for disallowed models in proxy(), and catalog endpoints filter the list from Bifrost's live /v1/models. Client UI filtering alone is insufficient. CFACTORY_ORG_MODEL_DENYLIST remains only as an optional emergency env overlay.

Metering

proxy() reads x-cfactory-projectid (stripped before the Bifrost hop) and, after the upstream response, records a cp_usage_events row asynchronously so it never blocks the token stream:

  • Non-stream JSON: parse usage.prompt_tokens / completion_tokens (and OpenAI input_tokens / output_tokens aliases).
  • SSE: tee the body and parse usage from the last data: chunk while still streaming to the client.

Cost = (prompt / 1e6) * input_per_m + (completion / 1e6) * output_per_m from the org policy (else 0). Usage and overview spend read this ledger; GET /api/profile/balance returns credit_grant minus period spend (not Stripe).

Provider policy

FlagWho sets itMeaning
providers.enabledCFly staff (per org)Master switch
providers.allowCustomOrg admin (only if enabled)Users may add local custom providers
providers.catalogOrg admin (only if enabled)Org provider catalog

Admin UI: Governance → Providers. Staff toggle lives on the Organization page when profile.user.staff is true.

Admin UI kit

packages/cfactory-admin uses React + shadcn + Tailwind, dark-first, primary #d2006f (CFly brand pink). It does not use Solid cfactory-ui.