v0.10.0 — hardening release
Hardening release from a full-package review: parallel × agents, parallel × crash recovery, interrupt payload integrity, and operational scale. Run php artisan migrate after upgrading — schema changes now always ship as additive migrations (this release adds hot-path indexes, drops the foreign key constraints, and repairs the v0.8 → v0.9 timeout_at gap).
Breaking (flagged per 0.x policy):
- Unaliased
evaluate()steps are now id'd by the bare class basename (wasevaluate:{Basename}), sooutput(Target::class)addresses the loop's checkpoints like any other step's. This changes those workflows' definition hashes — in strict drift mode, in-flight runs refuse to resume after deploying the upgrade (passas: 'evaluate:{Basename}'to keep the old id). awaitHuman()schemas containing closures or non-Stringable rule objects (Rule::enum,Password::min) now throw at definition time — they silently degraded to empty constraints through JSON persistence. Stringable rules (Rule::in(...)) are cast to their string form, which also corrects those definitions' hashes.- Duplicate explicit
as:aliases throw instead of being silently renamed. - Registering a different definition under an existing workflow name throws;
WorkflowRegistry::forget()is the explicit escape hatch. resume()/deliverEvent()payloads may no longer contain the engine-reservedstepskey.- Step targets must be real classes at definition time; a callback step returning non-null that isn't a
WorkflowStatenow fails instead of silently discarding the value. - An agent pausing on tool approvals inside a
parallel()branch fails the run with an explicit error (was silently recorded as completed); insideevaluate()bodies the pause now correctly parks the run andresume()continues the loop. - Lifecycle events implement
ShouldDispatchAfterCommitandSerializesModels;WorkflowFaileddrops its Throwable at the serialization boundary (queued listeners readfailure_reason). - The
run_idforeign key constraints are removed; deleting aWorkflowRunmodel still cascades to its steps and interrupts (query-builder mass deletes bypass model events).
Fixed:
- The default parallel merge deep-merges the engine-owned
stepssubtree per step id — two or more agent branches without amerge:closure always conflicted and could never complete. - Crashed parallel fan-outs are recoverable:
retry()supersedes all in-flight audit rows, the duplicate-delivery guard throws instead of returning the input snapshot as a branch result (sync mode silently completed with a crashed branch's work missing), and the batch completer only merges the current fan-out generation. cancel()(or a failure) stops queued branches from executing full agent turns against a dead run.- Strict definition drift is enforced in branch jobs, the batch completer, and in
resume()/deliverEvent()before the response is consumed. - Queue redeliveries of steps that are still executing (
retry_aftershorter than the step) no longer fail the run and discard the original attempt's result.
Added:
awaitEvent($event, schema:)— validates delivered payloads and strips undeclared fields.assertStepRanTimes()on the fake; all workflow-name assertions accept class names likestart()does.audit.step_outputconfig (full|minimal) for step audit snapshot size.- The sweeper chunks its scans, batches in-flight checks, and debounces re-dispatch to once per staleness window.
Full details in the CHANGELOG. 145 tests, Pint + PHPStan clean.