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.
| Owner | Paths | Rule |
|---|---|---|
| OSS | actionrail/**, tests/** | Runtime behavior and compatibility changes land upstream first. |
| OSS | control-plane/agents/**, control-plane/control_plane/** | Shared API, models, migrations, and local Console behavior land upstream first. |
| OSS | frontend/src/**, frontend/public/**, frontend build files | Core pages, registries, shared visual language, and accessibility fixes land upstream first. |
| OSS | docs/**, root packaging and shared scripts | Public behavior and shared release tooling stay aligned with core. |
| Cloud | cloud/** | Hosted authentication, workspace selection, billing, entitlements, Cloud settings, deployment, and enterprise-only UI. |
| Cloud | Cloud-specific CI/deployment files and UPSTREAM_VERSION | Private 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.navigationfor hosted navigation items;extensions.routesfor hosted pages or explicit core-route replacements;extensions.settingsfor hosted settings sections;providers.apiClientfor authenticated transport;providers.scopefor 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:
- authenticates the browser request;
- verifies membership in the selected workspace; and
- 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:
- For a Cloud-owned path, preserve the hosted behavior and adapt it to the new core contract.
- 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.
- Never resolve a conflict by adding a Cloud conditional to core.
- Run the unchanged OSS verification suite before Cloud-specific tests.
- Update
UPSTREAM_VERSIONonly 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_VERSIONis 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:
- Add a minimal
cloud/example/composition fixture in OSS CI so extension contracts are exercised without private code. - Add the
UPSTREAM_VERSIONancestry and reverse-import checks to Cloud CI. - Keep Cloud dependencies and migrations in Cloud-owned packages rather than editing shared manifests and apps.
- 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.