Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
bd69d77
feat(admin): firefly.admin.table.* settings for page size, scrollport…
Sep 23, 2026
01081c6
fix(web): shorten every trace frame against the roots the trace itsel…
Sep 23, 2026
14b9258
feat(admin): a ListingQuery that bounds page, size, sort and search a…
Sep 23, 2026
0d45452
fix(web): derive error-page roots from the outermost vendor segment
Sep 23, 2026
7b618ed
feat(admin): a ListingPage and an in-memory listing engine with a sta…
Sep 23, 2026
e615296
fix(web): spell error-page base roots with both separators, so Window…
Sep 23, 2026
aff3185
fix(admin): rank emptiness outside the direction flip, and share one …
Sep 23, 2026
0c70934
fix(web): guard the scheme of every operator-supplied error-page URL …
Sep 23, 2026
96e6187
fix(admin): compare the scalar string, not the raw value, in the unpa…
Sep 23, 2026
dc2c884
fix(web): refuse every error-page URL a browser resolves off this ori…
Sep 23, 2026
5dfa4cd
fix(admin): choose the row comparison once per column, so a mixed col…
Sep 23, 2026
358e90e
feat(admin): a typed column vocabulary that computes its own colgroup…
Sep 23, 2026
7462d24
fix(web): trim the whitespace a deployment pads an error-page URL wit…
Sep 23, 2026
cc9d828
fix(admin): lay listings out with a fixed table and a computed colgro…
Sep 23, 2026
4667c7c
feat(web): the error report carries the 405's verbs, the authored sen…
Sep 23, 2026
afce35a
fix(web): withhold the 404 sentences Laravel itself generates, and st…
Sep 23, 2026
e3ca8b3
test(admin): cover the pager's paged branch and make the page clamp o…
Sep 23, 2026
ea8fc2d
fix(web): the trace header names the untrimmed stack, and the referen…
Sep 23, 2026
021ece3
test(admin): pin the state the two listing forms carry, and correct t…
Sep 23, 2026
77a0a6d
feat(web): draw each trace frame on one line and put dependency frame…
Sep 23, 2026
e203cab
fix(admin): emit a flexible column width as a percentage the fixed-la…
Sep 23, 2026
af9d34f
fix(admin): give the table wrapper a height so the sticky header fina…
Sep 23, 2026
61985fa
fix(web): keep a frame's method name out of the ellipsis and raise th…
Sep 23, 2026
06e06b2
fix(admin): put the table scrollport opt-out on the panels that take …
Sep 23, 2026
a3444bd
fix(web): ellipsise a trace frame's method name inside its row instea…
Sep 23, 2026
570fe32
feat(admin): page, sort and search beans, conditions and scheduled ta…
Sep 23, 2026
cae57f0
fix(admin): stop offering an ordering by an interval column that sort…
Sep 23, 2026
6477fa8
fix(web): open the dependency disclosure when the trace has no frame …
Sep 24, 2026
cdf5987
fix(web): the fact grid draws its own rules and the reference is prin…
Sep 24, 2026
2e36e08
fix(admin): search the beans catalogue by the interface names the pag…
Sep 24, 2026
4a08a86
fix(web): give the copy button a rejection arm and stop three documen…
Sep 24, 2026
c72d125
feat(admin): page the environment, config properties, caches and logg…
Sep 24, 2026
750d626
test(web): run the error page's only script in the browser lane inste…
Sep 24, 2026
65722ce
fix(admin): size the configuration listings from the controls they dr…
Sep 24, 2026
45d8e52
feat(web): the error page offers the right action per status and a 40…
Sep 24, 2026
b770c8f
fix(admin): let the configuration listings open unordered, the way ev…
Sep 24, 2026
31fc826
fix(web): re-issue the request that failed, not just its path, and sa…
Sep 24, 2026
b6f13ca
fix(web): offer "Try again" only where a link can keep the promise, a…
Sep 24, 2026
0211d2e
feat(admin): page metrics by meter, HTTP traffic newest-first and the…
Sep 24, 2026
518b468
fix(admin): let the OAuth2 Active column order by its counts instead …
Sep 24, 2026
f67a9dd
fix(web): re-issue the request under its front controller, and build …
Sep 24, 2026
bbea851
docs(error-handling): describe the production page this wave really r…
Sep 24, 2026
a6362cf
fix(web): the problem renderer answers with a document on any payload…
Sep 24, 2026
64f826e
fix(admin): size the HTTP traffic When column for the dated stamp its…
Sep 24, 2026
20a273a
fix(web): the degraded problem document keeps every member that canno…
Sep 24, 2026
58ea54f
fix(web): make the problem renderer total against a throwing accessor…
Sep 24, 2026
10ba9da
fix(admin): break a tied listing sort on the identifier, and build ev…
Sep 24, 2026
b0efb59
fix(web): the degraded problem document reports the throwable it swal…
Sep 24, 2026
adbcec2
fix(web): a caller that sent a wildcard or no Accept header gets prob…
Sep 24, 2026
6cb902b
fix(admin): size the data browser's datetime column, give its page-si…
Sep 24, 2026
6ece749
fix(web): stand aside for the three throwables Laravel resolves itsel…
Sep 24, 2026
dc66235
fix(admin): hide the data browser's search box where nothing is searc…
Sep 24, 2026
0152941
test(web): derive the throwables describes() stands aside for from th…
Sep 24, 2026
bbf7b74
docs(web): say that the problem fallback claims every caller that is …
Sep 24, 2026
cd368fd
fix(web): identify a problem occurrence with a root-relative instance…
Sep 24, 2026
59850f7
fix(admin): size the data browser's columns for the headers a schema …
Sep 24, 2026
280c46f
fix(web): guard the problem type-uri and bring every published docume…
Sep 24, 2026
7ec494b
test(browser): exercise the dashboard at sixty rows — the pager, a ti…
Sep 24, 2026
2ae1a92
fix(web): stop the root-relative problem instance from naming another…
Sep 24, 2026
ed4281e
test(browser): observe the reload the auto-refresh performs, and meas…
Sep 24, 2026
143b482
test(web): cover error shapes through the real request pipeline
Sep 29, 2026
345db9b
test(browser): measure error page height and exercise recovery controls
Sep 29, 2026
613c434
docs(web): finish the error surface reference and migration guide
Sep 29, 2026
02828bc
fix(web): keep custom problem codes inside one URI path segment
Sep 29, 2026
e99b720
docs(admin): document shared listing controls and all six table settings
Sep 29, 2026
99a0c35
merge(ux): bring the completed error surfaces onto current main
Sep 29, 2026
be444a6
fix(admin): distinguish row identities when friendly sorting ties
Sep 29, 2026
e856fe0
merge(admin): land the shared listing system on current main
Sep 29, 2026
2acc142
feat(admin): bound bean exploration with iterative graph analysis
Sep 29, 2026
7c2e590
feat(admin): inspect the compiled route binding contract
Sep 29, 2026
e67d1ea
fix(admin): count only final omitted neighbours and document bounds
Sep 29, 2026
0d24744
fix(admin): match validation failures and dispatch order to runtime
Sep 29, 2026
cb6f608
feat(admin): open routes as accessible contract pages
Sep 29, 2026
33e20da
test(admin): verify route detail navigation and document its contract
Sep 29, 2026
b16b6b9
fix(admin): keep route contract details and joins accurate
Sep 29, 2026
bfe9361
merge(admin): land the verified route contract detail page
Sep 29, 2026
caf703c
feat(admin): render navigable bounded bean explorer
Sep 29, 2026
e042f2f
test(admin): verify native bean exploration across browser engines
Sep 29, 2026
b52dc9b
merge(admin): integrate the bounded bean explorer with route details
Sep 29, 2026
4aef35c
merge(admin): land cross-browser explorer coverage after combined gates
Sep 29, 2026
279dc39
fix(openapi): align generated contracts and make the reference readable
Sep 29, 2026
8babd43
chore(release): prepare LaraFly 26.09.10
Sep 29, 2026
ceba211
fix(admin): allow for Linux header font metrics
Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,80 @@ All notable changes to LaraFly are documented here. This project uses CalVer (`Y

## [Unreleased]

## [26.09.10] - 2026-09-29

### Fixed

- **OpenAPI contracts:** derive path-pattern `404` responses and restrict inferred validation `422` responses
to typed request bodies. Included page controllers now preserve their actual JSON, HTML or redirect return
contract. Browser coverage checks every generated operation, tag, response and component, including nested
schemas and keyboard access on phones.
- **OpenAPI readability:** improve local Swagger text contrast for method and version badges, links, actions,
code examples, schema controls and constraints; verify expanded schemas with either system color preference.
Normalize native schema buttons and wrap operation controls to prevent phone overflow in WebKit.
- **Error pages:** shorten paths in symlinked deployments and test harnesses, bound the debug stack before
rendering, and group dependency frames behind a native disclosure. Frame summaries stay on one line on desktop and give calls a second line on phones;
text contrast meets 4.5:1 in both themes. The production facts grid has no empty colored cells and shows
the reference once, with selectable text and an optional clipboard enhancement.
- **Error URL safety:** validate configured home, sign-in, support and problem-type URLs at construction;
reject unsafe schemes, authority-relative paths and interior URL control characters.
- **Problem documents:** substitute invalid UTF-8 and return a degraded document if encoding or a serialization
callback fails. Wildcard, absent and unsupported Accept types receive problem+json while error-page
fallback is enabled. Laravel's own validation, authentication and carried-response exceptions retain
their native handling.
- **Migration:** problem `instance` values now begin with `/`, with unsafe path characters percent-encoded.
Clients comparing the old relative path must account for the leading slash.

- **Admin listings:** replace competing column rules with typed columns, fixed table layout and explicit
colgroups. Route paths no longer collapse into stacks of characters beside unused space. Rigid column
widths include cell padding and allow for Linux header-font metrics, and a bounded table scrollport makes
sticky headers work.
- **Stable paging:** append an ascending identity tiebreak so tied rows cannot move between pages. Clamp
stale out-of-range pages to the last page; a SQL-backed data listing may need one additional query.

### Added

- **Route detail:** permalinked, server-rendered route contracts with ordered caller/injected bindings,
resolver claims, binding-specific failures, bounded DTO trees, sibling comparison and duplicate warnings.
Gated wiring/configuration/API/traffic links, default responses, exception handlers, registered route
metadata and collapsed advice provenance make the compiled contract inspectable without running it.
`firefly.admin.routes.detail` and `firefly.admin.routes.advice` control the new surface.
- **Bean explorer:** server-rendered landing/search/focus/module states, native keyboard links, bounded hop
columns, exact overflow links, complete paginated catalogue and relations, module coupling metrics,
conditions and shortest entry-point chains. Iterative SCC analysis handles deep graphs and self-cycles.
- **Bean graph truthfulness:** stable competing factory identities across configurations, explicit unresolved
ambiguity instead of an arbitrary target, unknown factory scope and exclusion of unbound config DTOs.
- **Explorer settings:** focus depth/row/node/path/page budgets, starter/module budgets and catalogue page size.
The legacy `firefly.admin.graph.max-nodes` is still parsed but no longer controls drawing; **0 no longer
forces a list**. Use the catalogue or relation tables for tabular exploration.

- **Error navigation:** configured sign-in on 401, retry on GET/HEAD 5xx, and home/support links where
configured. Production ledes retain safe authored details and 405 pages name the allowed methods.
- **Error configuration:** documented `max-frames`, `home`, `sign-in`, `support`, `actions`, `copy-button`,
`authored-detail`, `problem-fallback` and `problem.type-uri` settings in the reference and module guide.
- **RFC 9457 type:** `about:blank` by default, a code-derived URI when an HTTP(S) base is configured, or an
omitted member when the setting is empty. Omitting `type` does not revert the corrected `instance` path.

- **Shared listing controls:** server-side paging, sorting and searching with validated, bookmarkable URL
state across routes, beans, conditions, scheduled tasks, configuration, runtime and data listings. Paired
listings preserve each other's state, and row-count controls have submit buttons for use without JavaScript.
- **Table settings:** `firefly.admin.table.page-size`, `page-sizes`, `max-page-size`, `max-height`, `density`
and `remember-scroll`. The offered size set is closed; the data browser applies its own bounds in series.
Auto-refresh keeps URL state; optional per-URL scroll restoration applies on reload and back/forward.

### Changed

- **`packages/admin` — the data browser's rows-per-page control offers the dashboard's set, and a `?size=`
outside it is refused rather than lowered.** `/firefly/data` used to draw its own `<select>` with `10`
among the literal options and cap whatever arrived; its listing query is now parsed against the shared
`firefly.admin.table.page-sizes` narrowed by `firefly.admin.data.max-page-size`, and that set is **closed**
— a size that is not a member falls back to `firefly.admin.data.page-size` instead of being clamped to the
nearest permitted one. So `?size=300` renders 25 rows rather than 200, and **a bookmark holding `?size=10`
renders 25 rather than 10**, because ten is no longer offered unless a deployment says so
(`FIREFLY_ADMIN_TABLE_PAGE_SIZES=10,25,50,100,200`, or `FIREFLY_ADMIN_DATA_PAGE_SIZE=10`, which forces its
own default into the set). `firefly.admin.data.max-page-size` is a plain cap only for a direct
`Firefly\Admin\Data\DataBrowser::list()` call, which is parsed against no query string.

## [26.09.9] - 2026-09-27

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<a href="docs/installation.md#requirements"><img src="https://img.shields.io/badge/php-8.3%2B-blue?logo=php&logoColor=white" alt="PHP 8.3+"></a>
<a href="docs/laravel-comparison.md"><img src="https://img.shields.io/badge/Laravel-13-FF2D20?logo=laravel&logoColor=white" alt="Laravel 13"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-green" alt="License: Apache 2.0"></a>
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-26.09.9-brightgreen" alt="Version: 26.09.9"></a>
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-26.09.10-brightgreen" alt="Version: 26.09.10"></a>
<a href="docs/contributing.md#conventions"><img src="https://img.shields.io/badge/PHPStan-max-8A2BE2" alt="PHPStan: max"></a>
<a href="pint.json"><img src="https://img.shields.io/badge/code%20style-Pint-F55247" alt="Code Style: Pint"></a>
</p>
Expand Down
34 changes: 25 additions & 9 deletions book/src-es/04-first-http-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,19 +421,25 @@ final class ProblemDetailsRenderer
{
// An absent settings object means the SAFE answer, not the open one — see the constructor.
$disclose = $this->settings instanceof ErrorPageSettings && $this->settings->disclose;
$typeUri = $this->settings instanceof ErrorPageSettings ? $this->settings->typeUri : ProblemType::BLANK;

$correlationId = CorrelationIdFilter::of($request);
$reference = TraceContext::referenceFor($request);
$exception = ProblemMapper::toFireflyException($e, $disclose, $reference);

// …
$payload = ErrorResponse::fromException(
$problem = ErrorResponse::fromException(
$exception,
instance: $request->path(),
// …
instance: ProblemMapper::instanceFor($request),
traceId: $reference,
timestamp: (new DateTimeImmutable)->format(DateTimeInterface::ATOM),
correlationId: $correlationId,
)->toArray();
// …
type: ProblemType::of($exception->errorCode(), $typeUri),
);
// …
$payload = $problem->toArray();

$headers = [
'Content-Type' => 'application/problem+json',
Expand All @@ -448,18 +454,24 @@ final class ProblemDetailsRenderer

// …
return new Response(
json_encode($payload, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
$this->encode($payload, $reference),
$exception->httpStatus(),
$headers,
);
}

// …
}
```

`application/problem+json` se fija aquí, en cada respuesta que este renderizador produce — ese tipo de medio *es* el contrato del RFC-7807, y un cliente tiene todo el derecho a ramificar sobre él. A su lado viajan dos identificadores, y son dos a propósito. `X-Correlation-Id` lleva el identificador que `CorrelationIdFilter` acuña o lee en el borde de la petición, y el documento repite ese mismo valor en `correlationId`, de modo que quien llama puede casar el cuerpo que tiene delante con su propio registro de peticiones. El `traceId` del documento es lo que responda `TraceContext::referenceFor()` — el identificador de traza W3C de la petición cuando el trazado está activado y esta petición trae uno válido, y el identificador de correlación cuando no lo trae —, así que la referencia que alguien cita nunca está vacía, y activar el trazado es lo único que cambia cuál de los dos lleva. Cuando de verdad hay un identificador de traza, además se hace eco de él en una cabecera propia (`X-Trace-Id` por defecto; un nombre de cabecera vacío desactiva ese eco). Ambos identificadores pasan *a través* de `ErrorResponse` en vez de escribirse sobre el array después, porque la lista de miembros del DTO es de donde se genera el componente OpenAPI publicado — un miembro añadido aquí sería uno que ningún cliente generado sabe decodificar.

El corte que hay encima del `return` oculta el resto del trabajo con cabeceras: las cabeceras que una `HttpExceptionInterface` ya trae se copian sobre la respuesta — el `Allow` de un `405`, entre ellas — y un `503` gana además `Retry-After: 5`.

`$this->encode()` es el cuerpo, y es total a propósito. Un byte que no es UTF-8 válido — el mensaje de un driver que cita una columna latin-1, una cabecera de la petición copiada a un miembro de extensión — se sustituye en vez de elevarse. Cualquier cosa que `json_encode` siga rechazando, como un `INF` que una aplicación puso en una extensión en el punto del throw, cae a un documento mínimo; y también cae ahí cualquier cosa que lance el código *propio* de un objeto codificado, porque codificar un objeto llama a ese código — un `jsonSerialize()`, un accesor de Eloquent — y lo que lance no llega a ser una `JsonException` siquiera, razón por la cual el respaldo captura `Throwable` y no sólo la de JSON. Ese documento conserva todos los miembros que la forma de arriba define — el estado, el título, el código, la categoría, la severidad, la frase y los identificadores de referencia — porque cada uno es una cadena que escribió esta clase y ninguno puede ser lo que `json_encode` rechazó. Conserva también los errores de campo, con su nombre y su frase intactos — un `422` que siguiera diciendo `category: validation` sin llevar miembro `errors` mandaría a un cliente generado a la rama de errores de campo sin nada que pintar — y conserva el *nombre* de cada miembro de extensión: un valor que no puede llevar se escribe como el nombre de su tipo (`"balance": "App\Models\Balance"`) en vez de borrarse, que es como el endpoint de configuración de `firefly/actuator` lleva respondiendo siempre a esta misma pregunta. Sólo se pierden dos cosas: el valor ilegible, sustituido por su tipo, y el `rejectedValue` de cada error de campo, que se descarta del todo porque el contrato entero de ese miembro es *esto es lo que enviaste* y la palabra `float` no la envió nadie.

Y la degradación queda registrada. El throwable que capturó el respaldo es una excepción cualquiera de la aplicación, así que se registra — por el `Psr\Log\LoggerInterface` opcional que el provider le pasa a este renderizador, con el `traceId` del propio documento en el contexto para que la línea de log y el cuerpo que tiene quien llama se puedan juntar, y dentro de su propio `try`/`catch` para que un canal de log que también esté caído no pueda lanzar fuera del manejador de errores. Un subsistema de manejo de errores que falla de forma invisible es justo lo que toda esta superficie existe para eliminar, y un renderizador que se tragara una excepción en silencio mientras publica un documento indistinguible de uno sano sería exactamente eso. Un renderizador que lanzara aquí, por su parte, fallaría mientras atiende el fallo, y quien llama no recibiría documento alguno.

Lo que `render()` deliberadamente **no** decide es en qué `FireflyException` se convierte un throwable cualquiera. Esa regla vive una clase más allá, en `Firefly\Web\Error\ProblemMapper`, porque la página de error HTML del Capítulo 10 necesita la respuesta idéntica y dos copias de ella acabarían dándole a un navegador y a un cliente de API códigos distintos para el mismo fallo:

<!-- source: packages/web/src/Error/ProblemMapper.php -->
Expand Down Expand Up @@ -494,7 +506,7 @@ Cinco brazos que cubren cuatro casos — el `405` tiene un brazo propio solo par

Una `FireflyException` — o una de sus subclases tipadas, como `ResourceNotFoundException` — se devuelve intacta y se renderiza con su propio `httpStatus()`, porque su mensaje lo escribió tu aplicación *para* el cliente; ese es justamente el sentido de la taxonomía. El propio límite de tiempo de ejecución de PHP se nombra aparte y responde `503` con una cabecera `Retry-After`, porque «el servidor detuvo esta petición a los N segundos» es algo sobre lo que quien llama puede actuar y un `500` desnudo no lo es. Una excepción HTTP de Laravel/Symfony — una URL que no coincide con ninguna ruta, un verbo que una ruta no acepta — conserva su **código de estado real**, así que una ruta no coincidente sigue respondiendo `404` y nunca un `500` engañoso; solo se reemplaza la redacción del propio enrutador por una frase escrita para una persona, y los verbos permitidos de un `405` pasan a un miembro `allowed` y a la cabecera `Allow`, donde un cliente puede leerlos sin analizar inglés. Cualquier otra cosa es un accidente, y `$disclose` — `firefly.web.problem.disclose`, por defecto `false` — decide si su mensaje puede publicarse siquiera: con él apagado el cuerpo lleva una frase fija que nombra la misma referencia que lleva el `traceId` del documento, y el mensaje real se queda en el log, que es donde el SQL de una `QueryException` y sus enlaces deben estar.

Pedir un monedero que nunca se abrió se renderiza así. Fíjate en `instance`: es `$request->path()`, que Laravel devuelve **sin** barra inicial, así que es `api/v1/wallets/wlt-999` y no `/api/v1/wallets/wlt-999` — una cosa pequeña, y exactamente el tipo de cosa pequeña que un cliente que compara cadenas hace mal.
Pedir un monedero que nunca se abrió se renderiza así. Fíjate en `instance`: el RFC 9457 §3.1.5 lo define como una **referencia** URI, y una referencia relativa se resuelve contra la URI base del documento — así que el `api/v1/wallets/wlt-999` pelado que devuelve `$request->path()`, servido desde `/api/v1/wallets/wlt-999`, identificaría `/api/v1/api/v1/wallets/wlt-999`. LaraFly publica la forma relativa a la raíz, que es el único trabajo de `ProblemMapper::instanceFor()`, y un cliente puede compararla con la ruta que pidió. Fíjate también en `type`: el RFC 9457 §3.1.1 dice que un `type` ausente *es* `about:blank`, y LaraFly lo escribe en vez de dejar que el lector tenga que saberlo — apunta `firefly.web.problem.type-uri` a una URI base y el `code` estable deriva uno real y abrible.

```json
{
Expand All @@ -504,7 +516,8 @@ Pedir un monedero que nunca se abrió se renderiza así. Fíjate en `instance`:
"category": "business",
"severity": "warning",
"detail": "Wallet wlt-999 not found",
"instance": "api/v1/wallets/wlt-999",
"type": "about:blank",
"instance": "/api/v1/wallets/wlt-999",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"correlationId": "0f7c9b2e-6b43-4f5e-9a1d-2c8e5f0a91b7",
"timestamp": "2026-06-07T10:30:00+00:00"
Expand All @@ -521,7 +534,8 @@ Un fallo de comprobación `#[Valid]` en `POST /api/v1/wallets` — un `owner_id`
"category": "validation",
"severity": "warning",
"detail": "Validation failed",
"instance": "api/v1/wallets",
"type": "about:blank",
"instance": "/api/v1/wallets",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"correlationId": "0f7c9b2e-6b43-4f5e-9a1d-2c8e5f0a91b7",
"timestamp": "2026-06-07T10:30:00+00:00",
Expand All @@ -543,7 +557,8 @@ Un intento de retiro se rechaza por dos vías distintas, y las dos **no dan el m
"category": "security",
"severity": "warning",
"detail": "Processing command [Lumen\\Application\\Command\\Withdraw] failed: Authentication is required.",
"instance": "api/v1/wallets/wlt-1/withdraw",
"type": "about:blank",
"instance": "/api/v1/wallets/wlt-1/withdraw",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"correlationId": "0f7c9b2e-6b43-4f5e-9a1d-2c8e5f0a91b7",
"timestamp": "2026-06-07T10:30:00+00:00"
Expand All @@ -560,7 +575,8 @@ El `403` queda reservado para un principal que **sí** ha iniciado sesión y aun
"category": "security",
"severity": "warning",
"detail": "Processing command [Lumen\\Application\\Command\\Withdraw] failed: You do not have permission to do this.",
"instance": "api/v1/wallets/wlt-1/withdraw",
"type": "about:blank",
"instance": "/api/v1/wallets/wlt-1/withdraw",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"correlationId": "0f7c9b2e-6b43-4f5e-9a1d-2c8e5f0a91b7",
"timestamp": "2026-06-07T10:30:00+00:00",
Expand Down
Loading
Loading