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.
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
- Organization artifacts never enter a git worktree (no writes to
.cfactory/,AGENTS.md, or marketplace-style project installs). - Org and user content coexist (skills, commands, rules) — same basename does not clobber user content.
- Free-form
mergeDeepof/api/configis rejected. Only allowlisted keys apply. - Every org query on the gateway is scoped to a verified membership (
WHERE organization_id = $orgafterrequireOrg). A claimedx-org-id/:orgis 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 intoConfig.hooks.urls+hooks.enforce(HTTPS indexes pulled into~/.cache/cfactory/org/<orgId>/hooks/— never written into the repo). Also accepted undercfactory.org.hooks.instructions(HTTPS URLs only)cfactory.org(models,providers,mcp,hooks, extra skill/command/instruction/hook URLs)
cfactory.org.models fields:
allow/deny/defaultModelagentModels— map of mode name →provider/model(e.g.plan→cfactory/opus)routingPreference—cost|balance|quality(Bifrost Auto routing bias; defaultbalance)requireAutoModel— when true, members may only usecfactory-auto/*(gateway 403 + client catalog filter); concrete allow/deny still applies to Auto routing targetsallowLocalModelOverrideByProject— map ofprojectId → boolean(resolved againstorg-bindings.json)allowLocalModelOverride— optional global fallback
When allowLocalModelOverride is false for the bound project:
- CLI applies
defaultModelandagentModels(falling back todefaultModelfor modes without an entry) and clears stickymodel.jsonper-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 orgallowCustom— org admin: members may add local custom providerscatalog— 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>(fromCFACTORY_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
allowLocalModelOverrideis false for the bound project, localmodel/small_model/subagent_model/agent.*.modeloverrides are ignored and orgdefaultModel/agentModelsare applied. - When unlocked, org
agentModelsfill missing localagent.*.modelentries 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.
consoleManagedProvidersis 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 OpenAIinput_tokens/output_tokensaliases), plus provider prompt-cache fields when present (prompt_tokens_details.cached_tokens,input_tokens_details.cached_tokens, Anthropiccache_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
| Flag | Who sets it | Meaning |
|---|---|---|
providers.enabled | CFly staff (per org) | Master switch |
providers.allowCustom | Org admin (only if enabled) | Users may add local custom providers |
providers.catalog | Org 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.