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.
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
- 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.instructions(HTTPS URLs only)cfactory.org(models,providers,mcp, extra skill/command/instruction URLs)
cfactory.org.models fields:
allow/deny/defaultModelallowLocalModelOverrideByProject— map ofprojectId → boolean(resolved againstorg-bindings.json)allowLocalModelOverride— optional global fallback
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), 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 anddefaultModelis 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.
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). - 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
| 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.