A generated, rotatable credential scoped to one sandbox's data store (003 T035, FR-018 – FR-021, research R3).
defmodule MyApp.SandboxCredential do
use AshSandbox.SandboxCredentialTemplate,
data_layer: AshPostgres.DataLayer,
domain: MyApp.Sandboxes,
repo: MyApp.Repo,
table: "sandbox_credentials",
sandbox_resource: MyApp.SandboxRecord
endContainment comes from the grant, not from concealment
FR-019 requires a credential issued to one sandbox be unusable from any
other sandbox or from the host. That rules out the obvious implementation:
the tenant's application must connect to its own database, so the
credential is necessarily readable by tenant code — the spec says so
outright (Story 3).
So the containment cannot come from hiding it. It comes from the credential
granting nothing anywhere else: one LOGIN role per sandbox, with privileges
only on that sandbox's own logical database (013-FR-007, 013-FR-008).
A conformance test asserting that reading the credential fails would be testing the wrong guarantee, and would fail a correct implementation (003 T032).
sensitive? true is the mechanism for FR-021, not a hint
Ash redacts sensitive attributes from inspect output and from error messages
— which is where credentials actually leak: a crashed changeset printed into
a log, an {:error, changeset} inspected in a test failure, a struct dumped
by an observer. Set on the attribute, it holds everywhere the value
travels, including code paths written later by someone who has never read
this requirement.
That last property is why it is the mechanism rather than a convention. A convention protects the call sites its author remembered.
Rotation replaces the secret, never the row
FR-020 requires rotation without rebuilding or destroying the sandbox. The
role identity is stable and only the secret changes (ALTER ROLE ... PASSWORD), with version incremented in place — see research R4.
Rotation does not have to be restart-free: the spec's own acceptance scenario says "takes effect without rebuilding or destroying" while its independent test says "works with the new one after a restart". Live replacement would require every generated application to re-read credentials at runtime, which is a stack assumption Principle VI forbids.