The outcome of a lifecycle action (003 T011, FR-025, FR-027).
defmodule MyApp.OperationRecord do
use AshSandbox.OperationRecordTemplate,
data_layer: AshPostgres.DataLayer,
domain: MyApp.Sandboxes,
repo: MyApp.Repo,
table: "sandbox_operation_records"
endAttribution is stored, not traversed
owner_ref, project_ref, and environment_ref are stored directly
rather than reached by walking sandbox → environment → project.
That looks like denormalisation and is not. A failed provision must stay attributable after the saga has rolled its environment row back (003 research R2) — and a failed provision is exactly when attribution is needed. Attribution that depends on the success path is attribution that vanishes at the moment it matters, and it fails silently, because the happy path it was tested on still works.
For the same reason sandbox_ref is nullable: a provision that failed before
the sandbox row existed still produced an operation worth recording.
failure_reason is the same closed set as the registry
FR-027 requires the sandbox-specific causes stay distinguishable rather than
collapsing into one generic error. A free-text field satisfies the type and
fails the requirement — see AshSandbox.RegistryTemplate.
No secret ever reaches here
FR-021. AshSandbox.SandboxCredentialTemplate marks its secret
sensitive? true, which is what keeps it out of inspect output and error
messages; this resource simply has nowhere to put one.