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/cfactory-cli) 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. Organization owners configure SSO (OIDC/SAML) via the admin Settings → SSO tab; Keycloak Organizations brokers per-tenant IdPs.

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.
  • hooks.urls + hooks.enforce (HTTPS indexes pulled into ~/.cache/cfactory/org/<orgId>/hooks/ — never written into the repo). Also accepted under cfactory.org.hooks.
  • instructions (HTTPS URLs only)
  • cfactory.org (models, providers, mcp, hooks, extra skill/command/instruction/hook URLs)

cfactory.org.models fields:

  • allow / deny / defaultModel
  • agentModels — map of mode name → provider/model (e.g. plancfactory/opus)
  • routingPreferencecost | balance | quality (Bifrost Auto routing bias; default balance)
  • requireAutoModel — when true, members may only use cfactory-auto/* (gateway 403 + client catalog filter); concrete allow/deny still applies to Auto routing targets
  • allowLocalModelOverrideByProject — map of projectId → boolean (resolved against org-bindings.json)
  • allowLocalModelOverride — optional global fallback

When allowLocalModelOverride is false for the bound project:

  • CLI applies defaultModel and agentModels (falling back to defaultModel for modes without an entry) and clears sticky model.json per-mode picks.
  • IDE disables model changes in Settings → Models, Mode Edit, and the chat model picker.

Auto virtual models (cfactory-auto/* and gateway-owned cfactory/smart) resolve as follows: cfactory/smart is orchestrated in the gateway (cheap classifier → concrete model). Other Auto tiers still resolve in Bifrost; the gateway injects org routingPreference and allowed concrete model ids on those requests.

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), the hooks index (hooks/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 org defaultModel / agentModels are applied.
  • When unlocked, org agentModels fill missing local agent.*.model entries only (user overrides win).
  • 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), plus provider prompt-cache fields when present (prompt_tokens_details.cached_tokens, input_tokens_details.cached_tokens, Anthropic cache_read_input_tokens / cache_creation_input_tokens).
  • SSE: tee the body and parse usage from the last data: chunk while still streaming to the client.

Cost is cache-aware: non-cached input (prompt − cache_read − cache_write) at input_per_m, cache read at cache_read_per_m (default 10% of input), cache write at cache_write_per_m (default full input), plus completion at output_per_m. Usage and overview spend read this ledger; GET /api/profile/balance returns credit_grant minus period spend (not Stripe). Admin Usage & cost reports cacheHitRate = sum(cache_read) / sum(prompt).

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.