Skip to content

v0.3.0 — SCD2 history mirroring + tenancy fail-closed hardening

Latest

Choose a tag to compare

@palermo-git palermo-git released this 15 Jul 03:24

AshReplicant 0.3.0

The Ash Replicant.Sink adapter for CDC mirror targets — the "ash_postgres of replicant."

This release adds SCD2 history mirroring and hardens multitenancy to fail-closed at
compile time
across five tenant-isolation vectors — every one caught by tests proven to go
red on the defect, and by cross-vendor review.

✨ Added

  • SCD2 history mirroring (history_strategy :scd2) — opt a resource into validity-windowed
    history: instead of overwriting current state, mirror a source table into a host-defined
    version table (close-current + insert-version). Effect-once, fail-closed multitenancy,
    value-free boundaries, and route-through-Ash are all preserved. Adds the ValidateHistory
    compile verifier and on_truncate :close. (Audit-log needs remain served by AshPaperTrail on
    an SCD1 mirror.)

🔒 Security — multitenancy fail-closed

A multitenant mirror must never write rows unscoped. Five gates now enforce that — four at
compile time, one at runtime. See
ADR-0001
for the full model.

  • A declared tenant source now requires an Ash multitenancy block — for both
    tenant_attribute and tenant_mfa. Without one, Ash silently ignores the tenant: the sink
    passes and mirrors every tenant into one unscoped table. ValidateMultitenancy fails the build
    closed (any strategy, incl. a global? block, satisfies it).
  • A false-resolved tenant now fails closed. Ash treats a falsy tenant as no scoping, so a
    tenant_mfa returning false previously wrote unscoped. It now returns :tenant_required,
    like nil.
  • Sink-selected actions can no longer bypass tenancy. The new ValidateActionMultitenancy
    rejects multitenancy :bypass / :bypass_all on the host's primary read, create, destroy, and
    the SCD2 close action — a :bypass read would otherwise let a bulk_update / bulk_destroy
    match and mutate another tenant's rows.
  • The multitenancy discriminator column is now shape-checked. Under strategy :attribute,
    ValidateMultitenancy rejects a sensitive-classified or binary-storage-typed attribute (it
    would store/compare a mismatched value and silently mis-scope). AshCloak-encrypted attributes
    are already rejected by Ash's own verifier.

📦 Install

{:ash_replicant, "~> 0.3.0"}

Full changelog:
https://github.com/baselabs/ash_replicant/blob/main/CHANGELOG.md