Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
4cff364
feat(orchestration): register the firefly/orchestration package in th…
Oct 13, 2026
eb3620b
feat(orchestration): execution enums, topology layering, conditions a…
Oct 13, 2026
aa45388
feat(orchestration): the execution context and its JSON-safe checkpoi…
Oct 13, 2026
1c93cbc
fix(orchestration): refuse cyclic and over-deep step results instead …
Oct 13, 2026
96959a2
fix(orchestration): budget the checkpoint envelope in the depth guard…
Oct 13, 2026
90505e2
feat(orchestration): compiled argument injection, a resilience-backed…
Oct 13, 2026
259c462
feat(orchestration): lifecycle hooks, a metrics seam, EDA lifecycle e…
Oct 13, 2026
1136e9e
fix(orchestration): stamp the EDA x-correlation-id and partition_key …
Oct 13, 2026
d2a80ad
feat(orchestration): the execution persistence port with memory and c…
Oct 13, 2026
0f7cfa1
feat(orchestration): settings and their reference, execution reports,…
Oct 13, 2026
8f4d3cf
feat(orchestration): saga attributes, validated definitions, the regi…
Oct 13, 2026
50cbe93
feat(orchestration): the five saga compensation policies and the comp…
Oct 13, 2026
fd17998
fix(orchestration): run compensation handler retries through the saga…
Oct 13, 2026
3f672ee
fix(orchestration): fall back to the default compensation breaker onl…
Oct 13, 2026
0e7257b
fix(orchestration): throw a CompensationException when the circuit-br…
Oct 13, 2026
45770aa
feat(orchestration): the saga engine — layered execution, deadlines, …
Oct 13, 2026
b3bcfad
fix(orchestration): refuse to start a saga under a correlation id tha…
Oct 13, 2026
e02ce4b
feat(orchestration): saga composition with data flow and compensation…
Oct 13, 2026
8212410
fix(orchestration): report the composition members a rollback could n…
Oct 13, 2026
21e636e
feat(orchestration): TCC transactions with ordered participants, per-…
Oct 13, 2026
3576fbf
fix(orchestration): checkpoint a required TCC failure under CANCEL so…
Oct 13, 2026
b0a6ccd
fix(orchestration): honor the TCC composition compensation policy and…
Oct 13, 2026
d2177ef
fix(orchestration): cancel TCC participants whose try timed out or wa…
Oct 13, 2026
2e8dd1e
feat(orchestration): workflow attributes, validated definitions with …
Oct 13, 2026
c61a49e
fix(orchestration): validate every workflow step's wait at definition…
Oct 13, 2026
5b48e36
feat(orchestration): durable workflows with signals, timers, children…
Oct 13, 2026
29b9af8
fix(orchestration): keep a cancelled or compensated parent from being…
Oct 13, 2026
80860fc
fix(orchestration): re-check a waiting parent's child on every drive,…
Oct 13, 2026
1301d88
fix(orchestration): record a child's origin run so a child that conti…
Oct 13, 2026
a45d362
feat(orchestration): portable database persistence and dead-letter st…
Oct 13, 2026
f0a2357
fix(orchestration): rewrite search attributes by exact key and upsert…
Oct 13, 2026
a80a6fb
feat(orchestration): scan sagas, TCCs and workflows into a compiled m…
Oct 13, 2026
4b077b4
feat(orchestration): auto-configure the engines, register and validat…
Oct 13, 2026
d6b13dd
fix(orchestration): derive a trigger execution's correlation id from …
Oct 13, 2026
8a596f6
feat(orchestration): recovery that resumes or compensates killed exec…
Oct 13, 2026
8cd8445
fix(orchestration): finish an interrupted workflow compensation on re…
Oct 13, 2026
eee42a9
fix(orchestration): re-check a stale execution under its lock before …
Oct 13, 2026
3e164ee
fix(orchestration): keep a timer that came due between its wait check…
Oct 13, 2026
b87a153
feat(orchestration): an orchestration actuator endpoint and health co…
Oct 13, 2026
15879d3
feat(orchestration): an opt-in REST surface to start, inspect, signal…
Oct 13, 2026
5389fe6
feat(observability): meter and span adapters for sagas, TCC and workf…
Oct 13, 2026
9308f45
docs(orchestration): the orchestration guide, its links, the observab…
Oct 13, 2026
224f02a
docs(orchestration): stop claiming SQL Server is proven until its int…
Oct 13, 2026
b6792ef
test(orchestration): run ASYNC workflows through a real Laravel queue…
Oct 13, 2026
ba9eef3
test(orchestration): cover the #[CompensationStep] workflow form in t…
Oct 13, 2026
236bbf6
test(samples): register the firefly:eda:consume provider in the lumen…
Oct 13, 2026
0ff0dd4
fix(orchestration): end a saga whose compensation stopped part-way CO…
Oct 13, 2026
b9490a1
fix(orchestration): park a due workflow the tick fails to drive max-r…
Oct 13, 2026
f4a7684
fix(orchestration): log a tick failure by exception class, execution …
Oct 13, 2026
5591c6d
fix(orchestration): count nested drives and settlements of one workfl…
Oct 13, 2026
cc89c78
fix(orchestration): replay the child completions a failed cancel or c…
Oct 13, 2026
4ed7299
Merge remote-tracking branch 'origin/main' into feat/orchestration
Oct 13, 2026
5ad50e3
test(orchestration): read integration DSNs through IntegrationDsn and…
Oct 13, 2026
24be842
fix(orchestration): write search attributes by exact primary key, so …
Oct 13, 2026
a5394ed
docs(orchestration): state that SQL Server is proven by the CI integr…
Oct 13, 2026
de0739c
docs(cli): list the six firefly:orchestration commands and recount th…
Oct 13, 2026
44a6e45
test(testing): open every SQL Server integration connection unpooled,…
Oct 13, 2026
4e0b17d
test(data): say what the deadlock test's parent session saw when the …
Oct 13, 2026
a8bc39f
test(integration): put SQL Server's lock wait back after each test th…
Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@ All notable changes to LaraFly are documented here. This project uses CalVer (`Y

### Added

- **Orchestration (`firefly/orchestration`):** sagas with five compensation policies, compensation error
handlers, a builder DSL and saga composition; TCC with ordered participants, per-phase timeouts and
retries and TCC composition; and durable workflows with dependencies, conditions, signals, timers,
wait-for-all/any, child workflows, continue-as-new, queries, suspend/resume, search attributes and queued
execution. Definitions are attribute-discovered, compiled by `firefly:cache` and validated at boot.
- **Orchestration persistence and recovery:** checkpoints at every step through a `memory`, `cache` or
`database` store (proven on PostgreSQL, MySQL, MariaDB, SQL Server and SQLite by the CI integration lanes),
a per-execution distributed lock, a scheduled recovery sweep that reports, resumes or compensates stale
executions, retention cleanup, and a dead-letter queue with retry.
- **Orchestration operations:** lifecycle and step events through `firefly/eda`, event-type triggers, cron and
fixed-rate schedules, the `orchestration` actuator endpoint and health component,
`php artisan firefly:orchestration:{list,show,signal,dlq,dlq:retry,tick}` and an opt-in REST surface.
- **Orchestration queued workflows:** ASYNC workflows are now tested end to end through a real Laravel queue.
A capstone checks that the `RunWorkflowJob` is pushed on `firefly.orchestration.workflow.queue.connection`
and `.name`, that `queue:work` runs it to `COMPLETED`, that a redelivered job is a no-op, and that the
`sync` connection runs the job inside the start. An integration test runs the same path on the `database`
queue against PostgreSQL, MySQL, MariaDB, SQL Server and a SQLite file, one CI integration lane each.
- **Orchestration workflow compensation steps:** the separate-method `#[CompensationStep(compensates: …)]`
form is now tested and documented. The scanner tests check that the method becomes the step's compensation
with the step result bound implicitly, and that a duplicate, a step with both forms and an unknown step are
refused. A capstone and an integration test on PostgreSQL, MySQL, MariaDB, SQL Server and a SQLite file
check that a failing discovered workflow runs it.
- **Orchestration failure parking:** a saga whose compensation stops part-way ends `COMPENSATION_FAILED` and
is dead-lettered, and a due workflow the tick fails to drive `firefly.orchestration.workflow.max-resume-attempts`
times in a row (default `5`) is parked `FAILED` and dead-lettered.
- **Observability:** `orchestration_executions_seconds`, `orchestration_steps_seconds`,
`orchestration_compensations_total` and `orchestration_dead_letters_total`, and INTERNAL spans per execution
and step behind `firefly.observability.tracing.orchestration.enabled`.
- **`firefly/eda-database` — the same-transaction outbox on every database.** `firefly.eda.provider=database`
writes the outbox row inside the aggregate's transaction on PostgreSQL, MySQL, MariaDB, SQL Server or SQLite, and
claims committed rows by leases through an `OutboxClaimStrategy` (SKIP LOCKED on PostgreSQL, MySQL 8.0.1+ and
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,7 @@ Full flag reference and generated-file contents: [CLI](docs/cli.md).

One published library, `fireflyframework/larafly`, contains the components under `packages/*` and the bundled
application skeleton. Its `replace` entries satisfy component requirements at the framework's version;
component names remain useful module boundaries, with their own test suites. The 35
component names remain useful module boundaries, with their own test suites. The 36
[module guides](docs/modules/) below group them by concern:

| Group | Module | Package(s) |
Expand All @@ -1097,6 +1097,7 @@ component names remain useful module boundaries, with their own test suites. The
| Eventing & Messaging | [EDA Brokers](docs/modules/eda-brokers.md) — RabbitMQ/Postgres/Kafka adapters; Postgres adds `LISTEN`/`NOTIFY` to the database outbox | `firefly/eda-rabbitmq`, `firefly/eda-postgres`, `firefly/eda-kafka` |
| Eventing & Messaging | [EDA Database Outbox](docs/modules/eda-database.md) — the portable same-transaction outbox on every database, concurrent consumers | `firefly/eda-database` |
| Eventing & Messaging | [Messaging](docs/modules/messaging.md) — the lower-level raw-bytes broker layer | `firefly/messaging` |
| Eventing & Messaging | [Orchestration](docs/modules/orchestration.md) — sagas, TCC, durable workflows, recovery, DLQ | `firefly/orchestration` |
| CQRS | [CQRS](docs/modules/cqrs.md) — `CommandBus`/`QueryBus`, the domain→integration-event bridge | `firefly/cqrs` |
| Security | [Security](docs/modules/security.md) — principal model, `HttpSecurity`, `#[PreAuthorize]`, JWT/OAuth2 | `firefly/security` |
| Security | [OAuth2 Client](docs/modules/security-oauth2-client.md) — OpenID Connect login with presets and discovery, PKCE, id-token validation, `OidcUser`, RP-initiated logout, client credentials, `Http::oauth2Client()` | `firefly/security-oauth2-client` |
Expand Down Expand Up @@ -1149,10 +1150,10 @@ genuine same-transaction outbox (RabbitMQ, Postgres, Kafka, and the portable dat
server, with integration lanes per server in CI) and the global `firefly new` installer. What's still ahead,
accurately:

- **More actuator endpoints.** Sixteen ship today — `/health`, `/info`, `/env`, `/beans`, `/conditions`,
- **More actuator endpoints.** Seventeen ship today — `/health`, `/info`, `/env`, `/beans`, `/conditions`,
`/mappings`, `/loggers`, `/scheduledtasks`, `/caches`, `/configprops`, `/metrics`, `/prometheus`,
`/process`, `/httpexchanges`, `/oauth2clients` when the authorization server is installed, and `/flags`
when feature flags are enabled — and
`/process`, `/httpexchanges`, `/oauth2clients` when the authorization server is installed, `/flags`
when feature flags are enabled, and `/orchestration` for sagas, TCCs and workflows — and
`firefly.management.server.port` moves the whole surface onto a second listener, with `ManagementPortGuard`
making the actuator refuse the application port and `php artisan firefly:management:serve` running that
second listener in development. Spring Boot's `/refresh`, `/threaddump` and `/shutdown` are still not
Expand Down
2 changes: 1 addition & 1 deletion book/src-es/02-dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ php artisan firefly:cache
```

```
firefly:cache — wrote 14 manifest(s) + 0 proxy(ies) to /path/to/my-app/bootstrap/cache/firefly
firefly:cache — wrote 15 manifest(s) + 0 proxy(ies) to /path/to/my-app/bootstrap/cache/firefly
```

Ya ejecutaste esto una vez, de forma indirecta — `firefly new` lo llama por ti en su script `post-create-project-cmd`, que es la razón por la que la aplicación del Inicio rápido arrancó correctamente sin que ejecutaras el comando a mano ni una sola vez. De aquí en adelante, cada vez que añadas o cambies un `#[Service]`, `#[Repository]`, `#[Configuration]` o cualquier otro atributo de Firefly, vuelve a ejecutar `firefly:cache` para recompilar el manifiesto; `php artisan firefly:clear` elimina la caché compilada y vuelve a la ruta de escaneo de desarrollo (más lenta, basada en reflexión).
Expand Down
2 changes: 1 addition & 1 deletion book/src-es/03-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ foreach ($this->classesIn($prefix, $dir) as $class) {
}
```

`ConfigManifestCompiler` escribe esa lista en `bootstrap/cache/firefly/config-properties.php` con la misma técnica basada en `var_export()`, sin reflexión, que usó el manifiesto de componentes. `php artisan firefly:cache` ejecuta este escaneo como uno de los doce pares escáner/compilador que orquesta — nunca invocas `ConfigPropertiesScanner` tú mismo.
`ConfigManifestCompiler` escribe esa lista en `bootstrap/cache/firefly/config-properties.php` con la misma técnica basada en `var_export()`, sin reflexión, que usó el manifiesto de componentes. `php artisan firefly:cache` ejecuta este escaneo como uno de los trece pares escáner/compilador que orquesta — nunca invocas `ConfigPropertiesScanner` tú mismo.

En el arranque, `Firefly\Cli\Boot\FireflyCacheServiceProvider` carga ese manifiesto compilado y se lo entrega directamente a `Firefly\Config\Registrar\ConfigRegistrar::register()`, que vincula el DTO de cada descriptor como singleton del contenedor en un solo paso:

Expand Down
6 changes: 3 additions & 3 deletions book/src-es/07-cqrs.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,11 @@ El tipo exterior te dice *qué operación del bus* falló; el tipo interior —

`HandlerScanner` es el **único** punto de reflexión en `packages/cqrs/src` (la misma invariante forzada por grep que el Capítulo 6 describió para `firefly/domain`). Recorre una vez las raíces PSR-4 de la aplicación, resuelve cada `#[CommandHandler]`/`#[QueryHandler]` (ejecutando exactamente la inferencia de `messageType()` que acabas de leer) en un `HandlerDescriptor`, y entrega la lista completa a `HandlerManifestCompiler`, que la `var_export()`a en un archivo PHP de array plano — la misma técnica sin reflexión que el Capítulo 2 introdujo para el manifiesto de componentes y el Capítulo 3 para `#[ConfigProperties]`.

Nunca ejecutas `HandlerScanner` tú mismo. Es uno de los **doce** pares escáner/compilador que `php artisan firefly:cache` orquesta en un solo paso, junto al de componentes/contexto, rutas, propiedades de configuración y (del Capítulo 9) el transaccional:
Nunca ejecutas `HandlerScanner` tú mismo. Es uno de los **trece** pares escáner/compilador que `php artisan firefly:cache` orquesta en un solo paso, junto al de componentes/contexto, rutas, propiedades de configuración y (del Capítulo 9) el transaccional:

```bash
$ php artisan firefly:cache
firefly:cache — wrote 14 manifest(s) + 1 proxy(ies) to /path/to/my-app/bootstrap/cache/firefly
firefly:cache — wrote 15 manifest(s) + 1 proxy(ies) to /path/to/my-app/bootstrap/cache/firefly
```

El archivo compilado aterriza en `bootstrap/cache/firefly/handlers.php`; `FireflyCacheServiceProvider` lo vincula directamente sobre la entrada del contenedor `HandlerManifest` — sobrescribiendo incondicionalmente el `HandlerManifest` **vacío** por defecto que `CqrsWiringProvider` vincula cuando todavía no existe ninguna caché (el mismo patrón de retirada al estilo `#[ConditionalOnMissingBean]` que el Capítulo 2 te mostró para los beans, aplicado aquí a todo un manifiesto compilado). `CqrsHandlerWiringPass` recorre entonces ese manifiesto en el arranque y llena el `HandlerRegistry` que conociste arriba con un invocador por descriptor — cada invocador resuelve su bean manejador **de nuevo desde el contenedor en cada despacho**, nunca en caché en tiempo de cableado, así que un manejador `#[Transactional]` como `OpenWalletHandler` siempre se observa a través de su proxy generado (Capítulo 9), nunca a través de la clase desnuda, sin proxy.
Expand All @@ -479,7 +479,7 @@ Equivócate en esto en una aplicación — añade un manejador, olvida volver a
| La tubería acotada | correlacionar → validar → autorizar → (obtener de caché, solo consultas) → resolver e invocar → (guardar en caché, solo consultas) → métricas |
| `CommandProcessingException` / `QueryProcessingException` | Envoltorios que preservan la categoría; el fallo original siempre es alcanzable vía `getPrevious()` |
| `HandlerRegistry` | Una clase de comando/consulta se mapea a exactamente un manejador — un registro duplicado falla ruidosamente en tiempo de cableado |
| `HandlerScanner` → `HandlerManifest` | El único punto de reflexión; compilado por `firefly:cache` (uno de doce pares escáner/compilador) en un manifiesto sin reflexión |
| `HandlerScanner` → `HandlerManifest` | El único punto de reflexión; compilado por `firefly:cache` (uno de trece pares escáner/compilador) en un manifiesto sin reflexión |

---

Expand Down
2 changes: 1 addition & 1 deletion book/src-es/08-eda-outbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ final class OrderService

Aquí el destino (`'orders'`) y el tipo de evento (`'order.placed'`) son cadenas deliberadamente distintas, y el patrón `'order.*'` coincide con la **segunda** — precisamente para que este ejemplo no pueda confundirse con evidencia de que los patrones alguna vez miran el destino.

`EventListenerScanner` (`packages/eda/src/Scanner/EventListenerScanner.php`) es el único sitio de reflexión en `firefly/eda`, recorre una vez las raíces PSR-4 de la aplicación y compila cada método `#[EventListener]` en un `EventListenerDescriptor`; `EventListenerManifest` carga el resultado sin reflexión alguna, exactamente el mismo idioma de `HandlerManifest` que te mostró el Capítulo 7. `firefly:cache` ejecuta este escáner como uno de sus doce pares, escribiendo `bootstrap/cache/firefly/event-listeners.php`; `FireflyCacheServiceProvider` lo vincula, sustituyendo el valor por defecto vacío con el que arrancaría una aplicación sin caché. `EventListenerWiringPass` recorre después ese manifiesto al arrancar — en *todo* proceso, tanto en la petición web como en el worker de cola — envuelve cada invocación de destino en el decorador de reintento/DLQ de más abajo, y llama a `$bus->subscribe($pattern, $wrapped)` por cada patrón, resolviendo el bean de destino **fresco desde el contenedor en cada despacho**.
`EventListenerScanner` (`packages/eda/src/Scanner/EventListenerScanner.php`) es el único sitio de reflexión en `firefly/eda`, recorre una vez las raíces PSR-4 de la aplicación y compila cada método `#[EventListener]` en un `EventListenerDescriptor`; `EventListenerManifest` carga el resultado sin reflexión alguna, exactamente el mismo idioma de `HandlerManifest` que te mostró el Capítulo 7. `firefly:cache` ejecuta este escáner como uno de sus trece pares, escribiendo `bootstrap/cache/firefly/event-listeners.php`; `FireflyCacheServiceProvider` lo vincula, sustituyendo el valor por defecto vacío con el que arrancaría una aplicación sin caché. `EventListenerWiringPass` recorre después ese manifiesto al arrancar — en *todo* proceso, tanto en la petición web como en el worker de cola — envuelve cada invocación de destino en el decorador de reintento/DLQ de más abajo, y llama a `$bus->subscribe($pattern, $wrapped)` por cada patrón, resolviendo el bean de destino **fresco desde el contenedor en cada despacho**.

---

Expand Down
2 changes: 1 addition & 1 deletion book/src-es/09-transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ Cuatro detalles, cada uno de los cuales acabarás necesitando:

El Capítulo 2 te enseñó la forma de una clase `#[Configuration]` y sus métodos fábrica `#[Bean]`. Durante una versión de este framework, toda aplicación LaraFly tuvo que escribir una de ellas a mano — un `#[Configuration]` cuyo único `#[Bean]` cargaba el `TransactionalManifest` cacheado — o `#[Transactional]` no hacía absolutamente nada. **Ya no necesitas esa clase**, y merece una página: el fallo que aquella clase sorteaba fue de los más afilados que ha tenido este framework, y su arreglo es una pequeña lección sobre cómo se resuelve cada artefacto compilado.

`php artisan firefly:cache` emite **tres** artefactos separados que lee la maquinaria de proxies. Ejecuta `TransactionalScanner`/`TransactionalManifestCompiler` — uno de los doce pares scanner/compiler — para escribir los datos del manifiesto en `bootstrap/cache/firefly/transactional.php`. Ejecuta `ProxyPlanCompiler` sobre el `ProxyPlan` fusionado para escribir `bootstrap/cache/firefly/proxy-plan.php` — el artefacto que decide qué beans reciben siquiera un proxy y qué advice ejecuta cada uno de sus métodos. Y ejecuta `ProxyClassGenerator` para emitir un archivo fuente `{Target}__FireflyTransactionalProxy` **por cada clase que el plan nombra** — cada clase que reclama algún `AdviceSource`, así que un bean que solo lleva reglas de seguridad de método también recibe uno — en un directorio `proxies/`, más un classmap `proxies.php`.
`php artisan firefly:cache` emite **tres** artefactos separados que lee la maquinaria de proxies. Ejecuta `TransactionalScanner`/`TransactionalManifestCompiler` — uno de los trece pares scanner/compiler — para escribir los datos del manifiesto en `bootstrap/cache/firefly/transactional.php`. Ejecuta `ProxyPlanCompiler` sobre el `ProxyPlan` fusionado para escribir `bootstrap/cache/firefly/proxy-plan.php` — el artefacto que decide qué beans reciben siquiera un proxy y qué advice ejecuta cada uno de sus métodos. Y ejecuta `ProxyClassGenerator` para emitir un archivo fuente `{Target}__FireflyTransactionalProxy` **por cada clase que el plan nombra** — cada clase que reclama algún `AdviceSource`, así que un bean que solo lleva reglas de seguridad de método también recibe uno — en un directorio `proxies/`, más un classmap `proxies.php`.

Durante mucho tiempo, nada cargaba el primero de esos tres. `DataAutoConfiguration` vinculaba un `TransactionalManifest` *vacío* incondicionalmente, el `transactional.php` compilado solo lo referenciaba su propia declaración, y por eso `hasProxyFor()` era siempre falso y `TransactionalBeanPostProcessor` devolvía cada bean sin envolver. `#[Transactional]` era un **no-op silencioso** en cualquier aplicación que no escribiera a mano su propia configuración de manifiesto — que es exactamente lo que aquel `#[Configuration]` manuscrito existía para hacer, y exactamente el modo de fallo del que avisaba la sección anterior, llegando desde el propio lado del framework.

Expand Down
Loading
Loading