AshSandbox.SandboxCredentialTemplate (AshSandbox v0.1.0)

Copy Markdown View Source

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
end

Containment 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.