Skip to main content

Typed Python IaC engine: deterministic Atlas-lang config, graph state, parallel reconciliation

Project description

Atlantide

Typed, deterministic Infrastructure-as-Code for Python.

Write infrastructure in real Python — type-checked by your IDE and mypy, not a templating language — and get the guarantees a config language exists to provide: the same config always produces the same plan, and the engine can prove it.

Atlantide rests on three ideas:

  • Enforced determinism. Configs are plain Python, but executed by Atlas-lang: a bounded interpreter with no clock, no randomness, no environment, no network. Two runs of the same config produce a byte-identical intermediate representation and a stable content hash. Not a convention — the interpreter cannot do otherwise.
  • Graph state with Merkle skip. Resources form a dependency graph. A two-phase Merkle input_hash lets apply skip unchanged nodes with zero provider calls, and reconcile independent nodes in parallel.
  • Per-field mutability. Every field is declared mutable(), immutable(), or computed(). Changing a mutable field is an in-place UPDATE; changing an immutable one is a REPLACE; computed fields never diff. What a change costs is visible in the type, before you run anything.

Install

pip install atlantide          # or: uv add atlantide

Python ≥ 3.11.

atlantide init myproject       # scaffold a project that already compiles
cd myproject && atlantide plan

init writes an atlantide.toml, a starter config, and a .gitignore that keeps the state database and the secrets keyfile out of git. The default template uses the local provider, so it applies with no cloud credentials at all.

A quick look

from atlantide.core import Stack, output
from atlantide.policy import enforce
from atlantide.providers.aws import S3Bucket, SqsQueue

enforce("require-tags", keys=["env"])
enforce("deny-destroy-in-protected", stacks=["prod"])

for env in ["dev", "prod"]:
    with Stack(env, region="eu-north-1", name_prefix="atlantide", tags={"env": env}):
        assets = S3Bucket("assets", versioning=(env == "prod"))
        jobs = SqsQueue("jobs", fifo=True)
        output("assets_arn", assets.arn)
atlantide plan  infra.py     # preview
atlantide apply infra.py     # reconcile, in parallel
atlantide apply infra.py     # again: all NOOP, zero provider calls

Reading another resource's output (assets.arn) returns a lazy Ref. That is what wires the dependency edge — no explicit depends_on, no string addresses — and it resolves to the real value at apply time.

Infrastructure that already exists

Declare it as above, then bind it to what is already running rather than building a second copy:

atlantide import                              # what is declared but not tracked
atlantide import prod:aws.S3Bucket:assets     # found by name
atlantide import prod:aws.Vpc:main vpc-0abc   # located by an id AWS assigned
atlantide plan                                # no changes

A resource whose live settings differ from what the config declares is not imported — importing it would mean the next apply quietly changes it. import prints what differs so the config can be reconciled first, or --allow-drift adopts it and lets the next plan show the update. Nothing about import creates, changes or deletes anything; the undo is atlantide state rm, which forgets a row and leaves the resource alone.

How it works

Every plan and apply runs one pipeline. Everything before the diff is pure and deterministic; everything after it touches the world.

flowchart TB
    subgraph pure["Deterministic — no I/O; same input, same bytes"]
        direction LR
        cfg["infra.py"] --> lang["Atlas-lang<br/>subset check + fuel-bounded eval"]
        lang --> ir["IR + canonical JSON (RFC 8785)<br/>stable content hash"]
        ir --> dag["Dependency graph<br/>Refs become edges, cycles rejected"]
        dag --> merkle["Two-phase Merkle<br/>input_hash per node"]
    end

    merkle --> diff{"Diff"}
    store[("State backend<br/>sqlite · s3+dynamodb · postgres")] -.->|prior hashes| diff
    diff --> plan["Plan<br/>ordered, policy-checked"]

    plan -->|plan| changeset["Changeset<br/>NOOP · CREATE · UPDATE · REPLACE · DELETE"]
    plan -->|apply| exec["Executor<br/>parallel, under a renewed lease"]

    exec -->|unchanged hash| skip["Skipped<br/>no provider call"]
    exec <-->|create · read · update · delete| prov["Providers<br/>aws · local · random · yours"]
    exec -->|fenced writes| store
  1. Atlas-lang validates the config against a Python subset — no while, class, dunder access, eval, or non-allowlisted imports — then evaluates it with a fuel budget and deterministic builtins only.
  2. Lowering turns evaluated resources, Refs and output()s into an IR graph. Refs become dependency edges.
  3. Canonicalization serializes that IR to RFC 8785 JSON and hashes it. Two runs are byte-identical, which is what makes a .atlas artifact portable.
  4. Graph + Merkle: cycles are rejected (Tarjan), then each node gets a two-phase input_hash in topological order — so a change to one resource propagates to everything downstream of it, and nothing else.
  5. Diff compares desired hashes against prior state, yielding a per-node action. Per-field mutability is what decides UPDATE versus REPLACE.
  6. Plan orders the actions (creates and updates topologically, deletes in reverse; a REPLACE is destroy-before-create), then policy bindings run against the changeset — a mandatory violation blocks apply before anything happens.
  7. Apply takes a lease over the reachable graph, reconciles independent nodes in parallel, skips Merkle-unchanged nodes entirely, and persists incrementally. On failure it rolls back completed nodes as a saga.

The pure half is why plan needs no credentials, why validate runs in a pre-commit hook, and why the same compiled artifact can be promoted from staging to production without re-executing the config.

Main features

Determinism

  • No clock, environment, or network in config — the interpreter has no such builtins.
  • Content-hashed IR; build emits a portable .atlas artifact with provider versions pinned, verify re-checks it.
  • Determinism is over (config, inputs) — only inputs the config actually read.

Plans

  • Unchanged nodes cost zero provider calls.
  • apply re-diffs under the lock and refuses if the executed set differs from the approved one (--allow-plan-drift opts out).
  • refresh reports which fields it actually checked; an unchecked field is never claimed in sync.
  • A resource the provider cannot find is reported but kept.

State

  • Backends: sqlite (default), memory, S3 + DynamoDB, Postgres.
  • Per-node leases, renewed for as long as a run lives.
  • Fenced writes — the store refuses a write from a lease that is no longer the holder.
  • Versioned schema, forward migration, refuse-newer. state backup / restore.
  • Ctrl-C rolls back rather than abandoning the run mid-graph.

Secrets

  • atlantide.secret("name") is a handle; plaintext resolves in memory at apply.
  • Sensitive outputs sealed at rest; logs and audit records redact by construction.
  • Value stores: AES-GCM keyfile, environment, SSM Parameter Store.

Escape hatches

  • --target narrows to a resource and its closure, --replace forces a recreate — both printed in the plan.
  • A targeted apply leaves unselected state byte-identical.
  • state rm forgets a row without touching the provider; state unlock breaks a dead run's lease.

CI

  • --json: stdout is exactly one JSON document, success or failure.
  • --detailed-exitcode: 0 no changes, 2 changes pending, 1 error.
  • --audit-log appends every run to JSONL, no-ops included.
  • Prompts are refused without a terminal; pass --confirm/-y.

Extensibility

  • Providers are ordinary Python packages discovered by entry point — the built-ins included.
  • Components (L2 constructs) publishable from a git repo, pinned to a commit and content hash, vendored locally.

Documentation

https://atlantide-org.github.io/

  • CLI — every command, targeting, exit codes
  • Configurationatlantide.toml, inputs, profiles
  • Remote state — S3 and Postgres backends, concurrency, secrets
  • Authoring — output combinators, explicit ordering, renames
  • Providers — what ships, and writing your own
  • Components — L2 constructs, publishing, consuming
  • API referenceatlantide.core and atlantide.engine
  • Architecture — what each package is for

Runnable examples: examples/aws/ and examples/components/.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

atlantide-0.2.0.tar.gz (620.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

atlantide-0.2.0-py3-none-any.whl (382.0 kB view details)

Uploaded Python 3

File details

Details for the file atlantide-0.2.0.tar.gz.

File metadata

  • Download URL: atlantide-0.2.0.tar.gz
  • Upload date:
  • Size: 620.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for atlantide-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b8e51382f5f580366e31fc126c83cf9341a09b7547d535134fde2e7875c89f8d
MD5 97384ab35758c40f60654214834a4431
BLAKE2b-256 11385538fc53cedf566ae45f1ea0edca0fdf274edb742e2975ec217dab66233e

See more details on using hashes here.

Provenance

The following attestation bundles were made for atlantide-0.2.0.tar.gz:

Publisher: release.yml on atlantide-org/atlantide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file atlantide-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: atlantide-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 382.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for atlantide-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3b5a8983f5f04060bc4dc447148f83b4bdf3775af026c12c066a5e5c5a66a75a
MD5 f709ae3bfac59495912b9415cdec966d
BLAKE2b-256 a44a94ac9ba6a6af3f267e0b982633ca052a03085b007cd91fe0b23300fb6a2c

See more details on using hashes here.

Provenance

The following attestation bundles were made for atlantide-0.2.0-py3-none-any.whl:

Publisher: release.yml on atlantide-org/atlantide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page