Imported from crbnos/carbon (
packages/ee/AGENTS.md). Install upstream withnpx skills add crbnos/carbon --skill ee. Copyright stays with the author.
@carbon/ee
Enterprise edition — integrations registry (incl. Ramp), accounting sync (Xero, QuickBooks Online, Rillet), plan gating, Slack, email, Jira, Linear, Onshape, the shared rules layer (storage + sales rules), the planning engines (MRP + finite scheduling), and SAML SSO. (Exchange rates are no longer an integration — the platform-global feed lives in @carbon/jobs update-exchange-rates.)
Always
- MUST wrap provider-originated database writes in
withTriggersDisabled()so SYNC subscriptions cannot echo them back to the provider. - MUST link external ids through
createMappingService()andexternalIntegrationMapping; do not add per-entity external-id columns. - MUST register server lifecycle hooks in
src/hooks.server.ts; integration config files are shared with browser bundles. - MUST use the operation-specific helpers from
@carbon/ee/ramp.serverfor Ramp state.patchRampSettings,patchRampOAuthCredentials,patchRampRefreshedTokens,patchRampConnection,patchRampWebhook, andpatchRampCursorown disjoint paths and delegate to the atomicpatchIntegrationState()RPC boundary. - MUST use
FEATURE_PLANSfor client and server plan gating.companyHasPlan()andrequirePlan()intentionally allow non-Cloud editions and bypass-listed companies. - MUST pass caller-created Supabase/Kysely clients into
runMrp(),runLocationSchedule(),runExpediteWhatIf(), and other planning entry points.
Ask First
- Adding a sync entity or provider — it requires an
AccountingEntityType, provider syncer registration inSyncFactory, direction policy, subscriptions, and reconciler coverage. - Adding an integration to
integrations, changingFEATURE_PLANS, or changingINTEGRATION_WHITELIST. - Changing Ramp state ownership or replacing
upsert_company_integration_patch; concurrent settings, OAuth, webhook, token, and cursor writers depend on path-level composition.
Never
- Never import
*.servermodules from integration config files;config.tsxis client-bundled. - Never full-replace Ramp metadata or Vault bags. Use
src/ramp/lib/state.ts; stale read/merge/write loses concurrent sibling updates. - Never implement provider DELETE as a generic assumption. Entity adapters must explicitly support and verify their remote lifecycle.
- Never hand-edit generated database types.
Validation Commands
pnpm --filter @carbon/ee test
pnpm --filter @carbon/ee typecheck
Key Exports
| Subpath | Provides |
|---|---|
. |
Integration descriptors/registry, defineIntegration, and secret-resolution helpers |
./accounting |
SyncFactory, provider adapters, mappings, posting policy, reconciliation helpers |
./planning |
MRP and finite-scheduling entry points; server-only in practice because they import DB code |
./integrations/secrets |
patchIntegrationState, Vault split/persist/resolve helpers, SECRET_KEYS |
./ramp.server |
Ramp client, schemas, service operations, money/coding helpers, and key-owned state patches |
./ramp/hooks.server |
rampOnInstall, rampOnUpdate, rampOnUninstall, rampHealthcheck |
./hooks.server |
getIntegrationServerHooks() registry |
./plan, ./plan.server |
Client/server edition and feature-plan gates |
./sso.server |
SAML connection, domain verification, session, and provisioning helpers |
./storage-rules, ./storage-rules.server |
Storage-rule schemas and server operations |
./jira, ./jira.server, ./linear, ./linear.server, ./onshape, ./paperless-parts |
Integration client/server seams |
./slack.server, ./stripe-connect.server, ./xero/hooks.server, ./quickbooks/hooks.server, ./rillet/hooks.server |
Integration-specific server seams |
Key Patterns
- Planning (
./planning,src/planning/): the two planning engines, relocated from the Supabase edge runtime to run in-process in Node.runMrp(client, db, payload)— Material Requirements Planning (formerly themrpedge function;src/planning/mrp/mrp.ts).runLocationSchedule/runExpediteWhatIf+ the window resolvers (resolveLocationWindows/resolveWorkCenterWindows/subtractIntervals) — finite scheduling (formerly reached via@carbon/database/scheduling;src/planning/scheduling/). Every entry point is dependency-injected: aKyselyhandle (and, for MRP, a service-role Supabase client) supplied by the caller, which authenticates first. Server-only — pulls inpg/Kysely +@logtape, so import from route actions,*.service.ts,*.server.ts, or@carbon/jobshandlers, never client code. Shared edge-lib deps are reached through@carbon/databasesubpath barrels (types →@carbon/database, postgres →@carbon/database/client,explodeBom→@carbon/database/mrp-engine). - Accounting sync: class-per-entity syncers in
accounting/providers/{xero,quickbooks-online,rillet}/entities/;SyncFactory.getSyncer()dispatches - Subscriptions are code-derived:
accounting/core/subscriptions.ts—REQUIRED_SYNC_SUBSCRIPTIONS+ idempotentensureProviderSubscriptions()(exported from./accounting), converged from the install/onUpdatehooks and the outbound sweep — never a write-once install artifact, never a migration backfill - Card charges represented once:
accounting/core/posting.tsand every charge syncer'sshouldSync()must agree onisChargeBackedCardTransaction()so a Ramp card spend pushes as either a native charge object OR a journal entry, never both - 60s cooldown:
SYNC_OPERATION_COOLDOWN_MS(accounting/core/operations.ts) — a just-Completed ledger op absorbsevent/webhookre-enqueues for 60s. Status-transition events bypass it via the non-cooldownpostingtrigger: a state change is never dropped by the cooldown - Truthful ledger: a drain no-op with no remote copy closes
SkippedviaskipOperation()(reason inerrorMessage), neverCompleted;Skipped → Pendingretry is allowed - Rillet idempotency keys are entity-scoped:
buildRilletIdempotencyKey({companyId, operation, localId})— the payload is deliberately NOT hashed, so a crash-retry with a drifted payload cannot double-create the remote document - Tie-out remote reads:
accounting/core/remote-journal.tsfetchRemoteJournalTotals()— provider-agnostic debit-signed per-account journal totals for the reconciliation tie-out - Dependency sync: transaction syncers use
ensureDependencySynced()for JIT deps (e.g. push customer before invoice) - Rillet contact import: the
actionsentry insrc/rillet/config.tsxposts to/api/integrations/rillet/import-contacts, which firesrillet-import-contacts(@carbon/jobs). It is the only on-demand PULL of master data —buildRilletSyncConfigstill forces customer/vendorpush-to-accounting, and the job enqueuespull-from-accountingledger rows by hand.RilletCustomerSyncer/RilletVendorSyncerimplementmapToLocal/upsertLocalfor it and extendRilletEntitySyncer; the pull rejections now live inRilletPushOnlyEntitySyncer(item + every transaction syncer). Importing exists to write the mapping row:upsertRemotereadsgetRemoteId(localId)first, so a Carbon sales invoice for an imported customer PUTs the original Rillet customer instead of creating a duplicate - Ramp state helpers:
src/ramp/lib/service.tsis a compatibility facade. Put connection/auth inconnection.ts, coding masters inchart-of-accounts.ts/cost-centers.ts, parties insuppliers.ts, PO/bill transport inspend.ts, confirms insync-confirmation.ts, remote webhook lifecycle inwebhooks.ts, and metadata/Vault writes instate.ts;@carbon/ee/ramp.serverremains stable - Integration pattern:
defineIntegration()→ config with id, name, settings, OAuth, actions - SAML SSO (
./sso.server,src/sso/):isSsoEnabled()(gate.ts— Enterprise edition ANDssoinAUTH_PROVIDERS) is the ONE flag; the connection lookups and admin mutations self-gate on it.provider.server.ts= GoTrue admin API wrappers +getSamlSpUrls;connections.server.ts=ssoConnectionlookups (each attaches a computeddomains: string[]of VERIFIEDssoDomainclaims),isSsoRequiredForEmail,getSsoAwareInviteLink, upsert/requireSso/deactivate mutations, and the domain-claim flowsaddSsoDomain/verifySsoDomain/removeSsoDomain;verification.server.ts= the DNS TXT ownership challenge (_carbon-challenge.<domain>→carbon-domain-verification=<token>,checkDomainVerificationwith pinned public resolvers, one-shot manual verify — no polling or re-verification);session.server.ts= amr-based session classification (getSsoProviderIdFromSessionfor enforcement, nevergetSsoProviderIdFromUser);provisioning.server.ts= identity linking + invite-first migration +deleteJitSsoUser(full removal of a rejected throwaway JIT user — auth user AND its trigger-createduser/userPermissionrows, guarded on zero memberships; takes aKysely<KyselyDatabase>param — callers pass their own db client) + the pre-seed helpersseedSsoIdentityForUser/backfillSsoIdentitiesForDomain/removeSsoIdentitiesForDomain(+ pureemailDomain/ssoProviderColumn). Pre-seeding is the account-linking fix underGOTRUE_DISABLE_SIGNUP=true: a row inauth.identitieswithidentity_data.email = lower(email)makes GoTrue link a SAML sign-in to the existing user via its email-column fallback — provider-agnostic (works for any IdP: Okta/Entra/OneLogin/Ping/…, whose NameID shapes differ), keyed on the generatedemailcolumn NOTprovider_id.verifySsoDomainbackfills every existing on-domain user when a domain is verified; the three account-creation flows (apps/erp/.../users.server.ts) seed newly-invited users;removeSsoDomaintears the domain's identities down. A DB guard trigger onauth.sso_domains(migration…_sso-domain-guard.sql,ssoReservedDomaintable) blocks registering an unclaimed/reserved domain. Only verified domains ever reach GoTrue. Full architecture + the provider-agnostic linking rationale:.claude/rules/authentication-system.md(Enterprise SAML SSO section); design:.ai/specs/2026-08-29-saml-sso-account-linking.md - Exports:
./accounting,./planning,./plan,./plan.server,./rules(client-safe storage + sales rule services/UI),./rules.server(both evaluators),./slack.server,./hooks.server,./sso.server,./jira,./linear,./rillet/hooks.server,./xero/hooks.server,./ramp.server,./ramp/hooks.server, etc.
Cross-References
.claude/rules/ramp-integration.md— Ramp OAuth, state ownership, sync families, and card posting..claude/rules/accounting-sync-handlers.md— provider sync/reconciliation architecture..claude/rules/authentication-system.md— SAML SSO and account-linking rationale..claude/rules/billing-system.md— plan and edition gating.packages/jobs/src/inngest/functions/integrations/— durable integration entry points.