D75 — DomainException optionally carries JSON:API source + meta (structured error-object members)
Architecture decision record, relocated verbatim from the retired single-file
docs/tenancy/decision log. Status, thematic clusters, and how to record a new ADR: the decision log index.
The client-facing error contract (errors[] rendered by JsonApiExceptionHandler) previously let a DomainException
carry only a stable code + a human detail. It now ALSO carries two optional JSON:API error-object members —
?array $source (e.g. ['pointer' => '/data/attributes/…']) and ?array $meta (a free-form machine-readable map) —
rendered onto the error object when present. Both default to null, so every existing throw site is unaffected;
this is a pure superset of the prior contract (the D22 JSON:API envelope). The first consumer is role management:
RoleComposer::composeOrFail() maps a RoleValidationException to a SINGLE ROLE_INVALID (422) error whose
meta.invalid_permissions lists EVERY offending permission key (RoleValidationException now COLLECTS all
unknown/cross-guard keys, each tagged unknown|cross_guard, rather than throwing on the first), plus
meta.missing_dependencies where relevant, and — when exactly one key is at fault — a best-effort
source.pointer=/data/attributes/permissions/{index} (ADJUDICATED: one error object with a meta map + best-effort
source, NOT one-error-per-key). The Scramble #[DomainError] attribute is unchanged (status/code/detail only); the
dynamic meta/source are runtime-only, not part of the static OpenAPI shape.