Skip to main content

Downstream Cloud development contract

ActionRail OSS is the upstream source of truth for the SDK, control plane, Console, public documentation, and shared build tooling. ActionRail Cloud is a private downstream distribution that adds hosted identity, workspaces, billing, and enterprise operations through explicit extension boundaries.

This page is the merge contract between those repositories. It is deliberately tracked in the OSS repository so a change to the contract is reviewed and versioned with the boundary it describes.

The dependency direction

Cloud may import, configure, and extend OSS code. OSS code must never import Cloud packages, refer to Cloud models, or branch on a hosted-edition flag.

Repository model

Use the Cloud repository as a normal downstream clone with two remotes:

git remote add upstream git@github.com:ToolJet/ActionRail.git
git fetch upstream --tags

Keep private additions in a top-level cloud/ tree wherever possible. Give Cloud-only Python and frontend code their own package manifests and lockfiles inside that tree, so hosted dependencies do not modify upstream-owned manifests on every update.

The Cloud repository should track an UPSTREAM_VERSION file containing the merged OSS tag and commit SHA. That file belongs only to the Cloud repository. Temporary merge reports, conflict notes, and patch queues may use .cloud-sync/; that local directory is ignored by OSS.

Do not add cloud/ to the OSS .gitignore. Ignored source is easy to omit from a review or build, and an inherited ignore rule would make a real Cloud implementation awkward to track.

Path ownership

Ownership determines where a change lands first. It does not prevent the Cloud repository from containing the upstream paths.

OwnerPathsRule
OSSactionrail/**, tests/**Runtime behavior and compatibility changes land upstream first.
OSScontrol-plane/agents/**, control-plane/control_plane/**Shared API, models, migrations, and local Console behavior land upstream first.
OSSfrontend/src/**, frontend/public/**, frontend build filesCore pages, registries, shared visual language, and accessibility fixes land upstream first.
OSSdocs/**, root packaging and shared scriptsPublic behavior and shared release tooling stay aligned with core.
Cloudcloud/**Hosted authentication, workspace selection, billing, entitlements, Cloud settings, deployment, and enterprise-only UI.
CloudCloud-specific CI/deployment files and UPSTREAM_VERSIONPrivate release and infrastructure concerns remain downstream.

If a Cloud feature requires editing an OSS-owned path, first ask whether the change is useful to every distribution:

  • If yes, implement and merge it in OSS, then merge upstream into Cloud.
  • If no, add or improve a narrow OSS extension point, merge that upstream, then implement the private behavior under cloud/.

Do not keep a long-lived Cloud patch to an OSS feature page. That is the highest-conflict shape and makes security fixes harder to inherit.

Core Console pages are separated by product feature behind the stable frontend/src/pages.jsx barrel. Control-plane endpoints follow the same pattern under control-plane/agents/api/, while agents.views preserves the existing URL import surface.

Supported extension boundaries

The following boundaries exist today and are the preferred Cloud integration points.

Console composition

frontend/src/App.jsx exports ActionRailConsole. A Cloud entry point supplies:

  • extensions.navigation for hosted navigation items;
  • extensions.routes for hosted pages or explicit core-route replacements;
  • extensions.settings for hosted settings sections;
  • providers.apiClient for authenticated transport;
  • providers.scope for the selected workspace; and
  • runtime providers for storage, confirmation, and environment-specific shell behavior.

Cloud should own its entry point and Vite configuration under cloud/frontend/, import the public composition root, and write its compiled assets to its own build output. It should not replace frontend/src/main.jsx or fork AppShell.

frontend/src/api.js exports createApiClient. Use getHeaders for short-lived authentication headers and getScopeId for the current workspace. Core pages receive the same injected client; they must not read Cloud authentication state directly.

Control-plane scope

The OSS control plane uses the implicit local scope. Cloud sets ACTIONRAIL_SCOPE_RESOLVER to an authentication-aware callable that:

  1. authenticates the browser request;
  2. verifies membership in the selected workspace; and
  3. returns a stable scope identifier.

Core views call resolve_scope_id() and must not know about Cloud users, organizations, plans, or sessions. SDK runtime endpoints continue deriving scope from the agent key; they never trust a browser-selected workspace header.

Cloud authentication middleware, models, URLs, and settings belong in separate Django apps under cloud/. A Cloud URL configuration may register private endpoints before including core URLs.

Database migrations

Published OSS migrations are immutable. Cloud models use Cloud-owned Django apps and their own migration namespaces; they must not add migrations to control-plane/agents/migrations/.

When a shared model needs to change, add and test its migration in OSS first. Cloud migrations may depend on a released OSS migration, but an OSS migration must never depend on a Cloud app.

Upstream sync procedure

Sync at least every OSS release and before starting a large Cloud feature. Prefer a merge commit over rebasing published Cloud history; the merge records exactly which upstream state was integrated.

git fetch upstream --tags
git switch -c sync/actionrail-<version>
git merge --no-ff upstream/<default-branch>

Resolve conflicts according to ownership:

  1. For a Cloud-owned path, preserve the hosted behavior and adapt it to the new core contract.
  2. For an OSS-owned path, prefer the upstream version. If Cloud still needs a patch there, stop and upstream the shared fix or extension point.
  3. Never resolve a conflict by adding a Cloud conditional to core.
  4. Run the unchanged OSS verification suite before Cloud-specific tests.
  5. Update UPSTREAM_VERSION only after all checks pass, then merge the sync branch through review.

Do not use a sequence of cherry-picks for routine synchronization. Cherry-picks lose ancestry, make later conflict detection noisy, and obscure whether a security fix is present.

Required Cloud CI gates

Every upstream-sync pull request should run:

pytest
npm --prefix frontend ci
npm --prefix frontend run lint
npm --prefix frontend run build
npm --prefix docs ci
npm --prefix docs run build

Then run Cloud unit, integration, migration, and end-to-end tests. CI should also verify that:

  • the commit recorded in UPSTREAM_VERSION is an ancestor of the Cloud branch;
  • OSS tests are not skipped or replaced downstream;
  • Cloud packages do not appear in imports under OSS-owned Python or frontend paths; and
  • the working tree is clean after code generation and builds.

Change and release rules

  • Shared bug fixes, security fixes, dependency upgrades, and framework support land in OSS first.
  • A Cloud pull request that depends on unreleased OSS work records the exact upstream commit until the next tag.
  • OSS public APIs and extension contracts change with migration notes. Cloud updates its adapters in the same sync pull request.
  • Cloud does not rewrite, squash, or rebase already shared upstream commits.
  • Emergency Cloud fixes may ship downstream first only when they affect private infrastructure. If the root cause is in shared code, follow immediately with an OSS fix and remove the private patch on the next sync.

Before Cloud development scales

The Console extension boundary is covered by unit tests for registry replacement, route precedence, provider injection, and scope propagation. Run them with npm --prefix frontend test.

The architecture has the essential seams, but these remaining safeguards will make synchronization more mechanical:

  1. Add a minimal cloud/example/ composition fixture in OSS CI so extension contracts are exercised without private code.
  2. Add the UPSTREAM_VERSION ancestry and reverse-import checks to Cloud CI.
  3. Keep Cloud dependencies and migrations in Cloud-owned packages rather than editing shared manifests and apps.
  4. Assign code owners for extension contracts and require both OSS-core and Cloud reviewers when they change.

These are guardrails around an already usable boundary. They should be completed before several teams begin changing OSS and Cloud concurrently.