A catalog of the errors and sharp edges a host actually hits, each with its
cause and fix. Deeper per-feature detail lives in usage-rules.md.
Multitenancy
tenant required for :context … (:tenant_required) — a :context-multitenant
operation ran with a nil/blank tenant, so no query executed. This is fail-closed
by design (never a base-database read). Fix: pass tenant: on every
read/write/load/stream (Ash.read!(…, tenant: org)), or set a default tenant
on the action/domain.
CDC :tenant_required halts the pipeline — a tenant-scoped mirror got a
:delete/PK-changing :update whose old_record lacks the tenant_attribute
column. Cause: the source table uses Postgres' default replica identity (key
columns only). Fix: ALTER TABLE <table> REPLICA IDENTITY FULL upstream.
:cross_database_transaction — a transaction opened on one ArcadeDB
database tried to write another (e.g. two :context tenants in one
Ash.transaction). Single-database sessions are by construction; restructure
so one transaction touches one tenant's database.
Sorts, filters, keyset
Ash.Error.Query.UnsortableField — sorting (or distinct_sort, or keyset
paging) by a :binary (base64 — not byte-order-preserving), :decimal
(lexicographic string order ≠ numeric), or composite-typed (:map, :struct,
:union, {:array, _}) attribute. Fix: model money as integer minor units;
sort by another field; see usage-rules "D27".
UnsupportedFilter — the filter used something ArcadeDB cannot push down:
like/ilike, attribute-to-attribute comparisons, an aggregate or module
calculation ref, a value comparison on a sensitive field (only is_nil is
allowed), a non-stored (skip-ped) field, or a compound temporal RHS
(if/arithmetic on the temporal side). The error is value-free (names operator
- field only). Fix per usage-rules "Query & filter push-down"; for deterministic
searchable encryption, model the column as a plain
:binary(notsensitive).
"not filterable" on a relationship filter — filtering a source on a
related field whose destination resource carries an authorizer. Ash's
IN-rewrite reads the destination without per-hop authorization, so AshArcadic
rejects it for every actor. Fix: filter/load the destination directly, or drop
the authorizer on that destination if appropriate.
KeyError inside Ash core (scope_refs) on a relationship-path string
function — an upstream Ash bug (documented); use a flat filter.
Keyset page 2 fails after a field policy — the cursor is computed from a field the actor cannot read (redacted). Sort keyset pages by a field the actor can read.
Writes & concurrency
StaleRecord on update/destroy — the tenant-scoped, filter-scoped match
found zero rows: cross-tenant same-PK, already-deleted, or an
upsert_condition that evaluated false (single-row contract). Expected
behavior, not data loss.
HTTP 503 / ConcurrentModificationException — optimistic-lock contention
on a vertex type's buckets. Autocommit statements already retry (server-side +
client jittered backoff; knob config :ash_arcadic, :write_conflict_retries).
Session (transaction: :batch) bulk conflicts surface at COMMIT where no
statement retry is safe. Fixes: Ash.bulk_* with transaction: false
(converges), pre-create hot types with more buckets
(CREATE VERTEX TYPE X BUCKETS 32, host-side), and check result.status.
Duplicate rows from concurrent upserts of the same NEW identity — ArcadeDB
enforces no identity uniqueness by default; two concurrent MERGEs can both
create. Fix: a unique index on the identity (host-side DDL) or serialize
writers.
upsert returned no row / AshArcadic requires a primary key… — an
upsert returned nothing (delete raced the write), or update/destroy ran on a
resource with no primary-key attribute to match. The latter is a resource
definition bug.
Values & encoding
Write rejected value-free, naming an attribute — the value is not
JSON-wire-encodable (typically a raw non-UTF8 binary nested inside a :map /
:list value). Fix: encode app-side (Base.encode64) or use a :binary-typed
attribute (top-level binaries are handled).
:decimal range filters rejected — decimals store as exact strings;
range/order comparisons would be lexicographic. Fix: integer minor units for
money that needs range/sort.
Case-sensitivity surprise — contains/string_starts_with/
string_ends_with map to ArcadeDB case-SENSITIVE predicates; :ci_string
semantics are not preserved. Fix: normalize case app-side.
Vector search
Fewer results than expected from a sparse index, silently — ArcadeDB sparse indexes do not cover rows written before the index was created. Create sparse indexes before loading data, or re-touch pre-existing rows.
max_vector_candidates exceeded (fails closed) — the tenant's candidate
set for :attribute-scoped search exceeded the ceiling (default 10 000). Never
truncates by design. Fix: narrow the pre-filter, raise
config :ash_arcadic, :max_vector_candidates, or prefer :context (physical
DB per tenant) for very large tenants.
Full-text index creation fails (SchemaException, HTTP 500) — ArcadeDB cannot
build a FULL_TEXT index over a property that was auto-created implicitly by
writes (a dynamic property). Declare it first:
CREATE PROPERTY <Type>.<prop> STRING (host-side SQL), then
Arcadic.FullText.create_index/4. Dense/sparse vector indexes tolerate implicit
properties, but declare yours anyway — explicit schema-before-index is the
reliable order.
Transactions
:transaction_begin_failed / :transaction_commit_failed — the ArcadeDB
session could not begin (connection/availability) or the commit failed (e.g.
MVCC conflict at commit). A failed commit rolls the session back automatically;
retry the action.
A spawned task can't see the transaction — transaction sessions are owner-process-only by design (Ash keeps actions in-process). Do not hand transactional work to spawned tasks.
CDC sink (AshArcadic.Replicant.*)
:empty_index — the sink's domains contain no mirror resource, so the
sink halts before opening a transaction (never silently advances the
watermark). Register the mirror resource on a domain listed in the sink's
domains:.
:sensitive_plaintext — an arriving Postgres column maps to a sensitive
target attribute and is not in the replicant skip. Halted value-free. List
the column in skip, or model the target as a plain :binary if it arrives
already-encrypted (searchable-encryption escape hatch).
:checkpoint_read_fault at pipeline start — the watermark vertex could not
be read, so the snapshot-vs-resume decision is undecidable; the pipeline halts
(operator retry) rather than guessing. Check ArcadeDB availability and the
checkpoint resource's client: (must target the same database as the mirrors).
:truncate_halt — upstream TRUNCATE on an on_truncate: :halt mirror.
Either accept the halt (fail-closed default) or declare on_truncate :mirror.
AshArcadic.Replicant.Apply … is undefined after adding replicant later
— the optional-dep compile gate needs a one-time rebuild:
mix deps.get && mix deps.clean ash_arcadic --build && mix compile
(see Upgrading).
Timeouts & hangs
An action hangs against a stalled ArcadeDB — arcadic commands have no
timeout by default. Set timeout: <ms> (and transport pool options) in your
client module's Arcadic.connect/3 — the data layer uses your conn as-is:
def conn do
Arcadic.connect(url, db, auth: {"root", pass}, timeout: 15_000)
end