OrkStageOrkStage

Architecture decisions

The six accepted OrkStage ADRs in brief. Placement slots, provider adapters, BullMQ workflows, tenancy, OpenTofu state and the toolchain.

Architecture decision records (ADRs) live in docs/adr/ of the platform repository. Each summary below links to the full record.

0001 Per resource placement through template slots

Accepted. Templates are shared across tenants, but each tenant has its own cloud accounts. So the provider, account and region cannot live in the template.

Instead, a template declares placement slots, each with a list of allowed providers. Every resource names a slot, and every instance binds each slot to { targetId, region }. The same template runs on one cloud or on several, depending only on the bindings. Networks bind only to resources in the same slot. Read ADR 0001

0002 Provider adapters, capability catalogs and simulation

Accepted. CloudProviderAdapter is the only way the platform touches a cloud. Its ensureResource call converges a resource to the desired state, so a retry cannot create a duplicate.

The mapping from a logical resource to a provider implementation is data: capability catalogs that list features, regional gaps and static prices. A deterministic mock provider simulates AWS, Azure or OCI with injectable failures, and the platform labels everything it simulates. One shared contract test suite runs against every adapter. Read ADR 0002

0003 Deployment workflow on BullMQ with persisted steps

Accepted. Temporal would add a cluster to operate. BullMQ reuses the Redis and Postgres that the platform already requires.

A job carries only the deployment id. Steps are database rows, created when the deployment is requested and updated before and after each action. Resuming a deployment means running the same job again, which skips the phases that already finished. Temporal is worth another look if long flows that wait on people, or very large fan out, appear. Read ADR 0003

0004 Tenancy, sessions and authorization

Accepted. All tenants share one database, and every row a tenant owns carries a tenantId. Isolation works in four layers: the request principal, query scoping by membership, a CASL check on the loaded record, and the audit log. A record from another tenant is reported as not found.

CASL rules are defined once. The API enforces them, and the console receives them only to decide what to show. Sessions are opaque and stored on the server, not JWTs. OIDC providers are plain configuration. Read ADR 0004

0005 OpenTofu execution and state

Accepted. Every tofu command runs in a throwaway container with a pinned image. Credentials exist only in that container's environment.

Each resource has its own state file in S3, locked with use_lockfile and encrypted. This keeps the platform's own DAG and isolates a failure to one resource. State holds no secrets: passwords come from ephemeral resources and travel through write only attributes. The same code serves real AWS and MiniStack. Read ADR 0005

0006 Toolchain choices

Accepted.

  • pnpm workspaces without Turborepo, because development machines block native binaries from node_modules.
  • TypeScript 7.
  • Prisma 7.10, not the 8.0 release candidate.
  • Zod 4, shared by request validation, the OpenAPI document and console forms.
  • ESM everywhere.

Read ADR 0006

0007 Profiles, tenant limits and template tags instead of plans

Accepted.

Plans and their entitlements are gone. Each tenant has optional limits that platform owners and admins set, and feature flags decide whether its templates may use several clouds, several regions, autoscaling and custom domains. Templates carry free form tags instead of a list of plans and a size profile (spec.plans and metadata.profile are ignored with a warning for one release). Named profiles of accounts and regions per environment replace the default cloud targets: new instances bind the slots they leave unbound from a profile. Read ADR 0007

Writing a new ADR

Copy the structure of an existing record: Status, Context, Decision and Consequences. Give it the next number and keep it short. To change an accepted decision, write a new ADR that supersedes it instead of editing the old one.

On this page