Goal: import from more than one Terraform state (or Atmos manifest set, or any other external source) into a single project, and keep each source's data addressable without the two colliding.
An import source is a project-scoped record — a URI plus a kind
(tf-state / atmos-manifests) and a reconciliation policy — that every
entity is imported under. The catalog — your entity types, cells,
relationship types, and Action definitions — is shared across every source in
a project, so there's nothing to keep in sync between sources. The graph
data is not: an entity's identity is (entity type, name, source), so two
sources in the same project can each hold a Server named web-01 without
colliding.
| Shared across sources (project-scoped) | Partitioned per source |
|---|---|
| Entity types, cells, relationship types | Entities |
| Action definitions | — |
apply of the catalog (schema) | Relationships still resolve project-wide — an edge can span two sources |
Sources are project-scoped. The CLI registers one automatically on your first
import terraform / import atmos against a given state — you rarely need to
register one by hand:
Registration is idempotent, keyed on (project, source URI). A second import
against the same URI refreshes the existing source rather than creating a
duplicate; a re-import that omits labels leaves whatever labels are already
on file untouched (only an explicit clear removes them).
?source=Every write against /entities names the source explicitly — there is no
default and nothing is auto-created. A write with no source fails closed:
Reads are project-wide by default — GET /<org>/<project>/entities returns
every entity across every source in the project — and source narrows a read
to one source when you need it:
Two sources in one project can each register an entity of the same type and
name — that's the whole point of source-partitioned identity. Direct entity
routes (GET/PUT/DELETE /entities/:name, /actions, /metrics, …) always
require ?source=, so they can never be ambiguous. A relationship's from
/ to is different: an edge's two ends legitimately live under different
sources (R3), so a Relationship item in an apply bundle names its
endpoints by bare entity name, resolved project-wide. If that name matches
rows in more than one source, the apply raises 409 AMBIGUOUS_ENTITY_NAME
rather than wiring the edge to whichever row Postgres happened to return
first:
Address the endpoint by its surrogate id instead once you hit this — the error
message names every colliding type@source candidate so you know which id to
use.
Deleting a source destroys every entity whose identity partitions by it, and — through those entities — every relationship and cell membership that references one of them:
An edge cascades off either endpoint: a relationship from an entity on the deleted source to an entity on a still-live source is destroyed too, even though the surviving entity is untouched. Re-import recreates the source and its entities from scratch.
One project, one shared schema, and as many import sources as you have
external states — each with its own partition of the entity graph, addressed
explicitly rather than picked implicitly. Edit the model once and apply
once; every source's entities are governed by it.
There's no per-source schema. An apply of the catalog changes the model for
every source in the project simultaneously; you don't (and can't) applyagainst a single source. This is deliberate — it's what keeps two sources'
entities from drifting onto different definitions of the same entity type.
source is a 400, never a default
There is no "current source" a write falls back to. Every write against
/entities (and instance-scoped routes under it — metrics, actions,
relationships) requires ?source= naming a source already registered in the
project. Register the source first (import-sources register, or let
import terraform / import atmosdo it for you) before writing entities
against it.
source field on an entity.