Atom

Data Model

Current schema overview for Atom's main database tables.

Atom stores security state in Postgres. All primary keys are UUIDs. Most objects have tenant_id so the same Atom instance can serve many domains or customers.

Data Model Diagram

What this means: entities prove identity with credentials. Roles and direct policies connect subjects to permission blocks. Permission blocks say where actions apply. Assignment guardrails prevent unsafe access state from being created. Audit logs record what happened.

Core Tables

TableSimple purpose
tenantsTop boundary for a domain, customer, or workspace.
entitiesPrincipals such as humans, devices, services, workloads, and applications.
credentialsPassword hashes, access-token hashes, scoped-token ceilings, and issued certificate records for entities.
sessionsRevocable login sessions referenced by JWTs.
resourcesProtected objects such as channels, rules, reports, alarms, or application resources.
principal_groupsWho-containers for entities that receive the same roles.
object_groupsWhere-containers for entities, resources, and child object groups.
rolesFriendly names for sets of permission blocks.
actionsOperation names such as read, publish, manage, and authz.check.
action_applicabilityDefines which actions are valid for which protected object kinds/types.
action_assignment_rulesDefines which entity kinds may be assigned which actions on protected object kinds/types.
permission_blocksThe access rule: scope, effect, optional conditions, and object boundary.
permission_block_actionsLinks actions to a permission block.
role_permission_blocksLinks permission blocks to roles.
role_assignmentsGives a role to an entity or principal group.
direct_policiesGives one permission block directly to an entity or principal group.
audit_logsImmutable history of security-relevant events.
certificate_crl_stateCached CRL state for the active mounted certificate issuer.

Aliases

Every tenants, entities, and resources row may carry an optional alias: a short, human-friendly handle used in place of the UUID. The UUID stays the canonical identity — aliases are a convenience layer for addressing and never appear as foreign keys, audit subjects, or authorization scope.

  • Slug shape. An alias is a lowercase slug of 1–63 characters using a–z, 0–9, and -, with no leading or trailing dash. It is case-folded on write, may not be UUID-shaped, and is validated both in the service and by a database CHECK.
  • Uniqueness. Tenant aliases are unique across the platform. Entity and resource aliases are unique within their tenant, so the same alias (for example watermeters) may be reused in different tenants.
  • Resolution. AliasService.ResolveAlias (gRPC) resolves a tenant alias plus an object alias to the underlying UUIDs in one call. Global entities and resources use the request's explicit global selector. Resolution is capability-neutral; the authorization gate is the subsequent authz check by UUID.

Aliases stay an alias, not a replacement: rename one and every UUID-keyed grant, session, and audit row is unaffected.

Entity And Credential State

Entities are the universal subject type. A user, device, service, workload, and application are all entities with different kind values.

Credentials belong to entities:

  • password credentials store an argon2 hash;
  • access-token credentials store an argon2 hash and a lookup identifier; unscoped rows are product API keys, while scoped rows carry a permission ceiling;
  • certificate credentials store issued certificate metadata and no private key.

Plaintext passwords, API key/access-token secrets, and generated leaf private keys are never stored.

Disable, Delete, and Retention

Disabling and deleting are different operations with different blast radius.

Disable is a reversible status change. The row stays live and listable; the subject is just blocked from acting (entities become inactive, profiles disabled, tenants inactive/frozen). Re-enabling restores it. In the UI, disabled-family states render amber as a warning.

Delete is a soft delete. The row is hidden immediately — a deleted_at tombstone is set, child sessions and credentials are revoked — but the data is not physically removed. Deleted rows are excluded from default (Live) listings and surface only under the Deleted filter. In the UI, delete renders red.

Soft-deleted rows are removed permanently only by the purge background job, which deletes tombstones older than the retention window (reusing the existing foreign-key cascades). Purge is disabled by default: until it is enabled, tombstones are kept indefinitely, so a delete is recoverable.

SettingEnv varDefault
Purge enabledATOM_PURGE_ENABLEDfalse
RetentionATOM_PURGE_RETENTION_DAYS90 days
Sweep intervalATOM_PURGE_INTERVAL_SECS86400 (daily)

A tenant can also be purged on demand via the purgeTenant mutation, which bypasses the retention window and is deliberately irreversible and admin-only.

Authorization State

The authorization model is split deliberately:

  • actions name the operation;
  • action_applicability says where the operation is valid, but does not grant access;
  • action_assignment_rules say whether access may be assigned to an entity kind;
  • permission_blocks define the actual rule;
  • roles bundle permission blocks;
  • role_assignments give roles to subjects;
  • direct_policies give one permission block directly to a subject.

This keeps scope and actions in one place: the permission block.

The canonical protected object kinds are entity, resource, group, tenant, role, policy, credential, audit_log, and signing_key. Sub-kinds are stored with a kind prefix, such as entity:device and resource:channel, so authorization checks, audit logs, and explain output use the same values.

Certificate State

Atom does not store CA certificates or CA private keys in Postgres in file issuer mode. CA files are mounted into the container and loaded during startup.

Postgres stores only issued certificate lifecycle state:

  • issued certificate rows in credentials;
  • revocation data in certificate credential metadata;
  • cached CRL bytes and CRL number in certificate_crl_state.

On this page