Skip to content

v0.3.2

Choose a tag to compare

@dberry37388 dberry37388 released this 19 May 12:08
· 394 commits to main since this release

Changed

  • Test suite verified against laravel/ai v0.6.8. Two test fixtures were relying on an implicit per-class agent fake fallback removed in v0.6.8; they now explicitly fake all agents in the swarm.

Added

  • swarm:relay --max-attempts=N: limits the number of drain iterations when used with --drain-until-empty. Without this flag the loop continues only while there is real progress; with it, the loop also retries through batches of pure transient failures up to N times total, making it suitable for clearing backlogs during a recovering queue outage. Iterations run consecutively with no sleep — size N accordingly.
  • swarm:health --durable outbox staleness check now reports three states: "no pending rows" (ok), "N pending rows, relay appears active" (ok), and "N rows aging past {threshold}s — is swarm:relay scheduled?" (warning). Previously the check was binary ok/warning with a single threshold.
  • swarm.durable.relay.stale_warning_threshold_seconds config key (SWARM_DURABLE_RELAY_STALE_WARNING_THRESHOLD_SECONDS). 0 falls back to 2 × reservation_timeout_seconds (backwards-compatible default).
  • Static "Relay scheduling" note row added to swarm:health --durable output. Reminds operators that swarm:relay must be scheduled. Status is note (informational only; does not affect exit code).
  • swarm:health --durable now includes an Outbox queue routing check: warns when outbox rows reference a queue_connection not present in config/queue.php. Rows with an unknown connection are permanently skipped at drain time; this surfaces them before they accumulate silently.
  • DrainResult gains two new fields: claimed (rows reserved in phase 1) and reclaimed (subset whose reserved_at was already set, indicating a prior relay worker did not complete dispatch). Both default to 0 (backwards-compatible). Custom DurableOutbox implementations may populate these fields when returning DrainResult; applications that only consume the result need no changes.
  • swarm:relay audit payload now includes claimed_count and reclaimed_count alongside the existing dispatched_count, skipped_count, and failed_count.
  • swarm.limits.max_metadata_bytes config key (SWARM_MAX_METADATA_BYTES; null = uncapped). Enforced via SwarmPayloadLimits::checkMetadata() at run start in SwarmRunner and DurableSwarmStarter. The truncate overflow policy does not apply to metadata; only fail fires when the limit is exceeded.
  • Documented recommended queue topology patterns in docs/maintenance.md: minimal (single queue), durable sequential (separate worker pool), and durable with parallel branches (third pool to prevent saturation deadlock on saturated step queues).
  • Added ## Metadata Governance section to docs/audit-evidence-contract.md covering the allowlist approach, custom sink redaction example, and scope clarification (sink-layer only; does not affect RunContext or database capture).

Fixed

  • swarm:relay / DatabaseDurableOutbox::drain() reported a false green when all entries in a batch failed transiently (queue driver down): total() returned 0, --drain-until-empty exited, and the command printed "No pending outbox entries were found." The new DrainResult::$failed counter tracks transient failures separately; the command now exits with status 1 and a descriptive warning when unresolved transient failures remain at exit, and the "no pending entries" message is only printed when the outbox is genuinely empty.
  • DatabaseDurableOutbox::drain() silently rerouted entries with an unknown queue_connection to the application default queue instead of treating them as permanently invalid. An unknown stored connection name is now an UnexpectedValueException (row deleted, reported, counted as skipped) — the same contract as an unknown dispatch_type. The previous behaviour undermined queue-isolation guarantees.
  • DatabaseDurableOutbox::dispatchEntry() blindly cast missing or non-integer step_index payload fields to (int) (giving step 0) and missing or empty branch_id fields to (string) (giving ''). Both cases are now validated and throw UnexpectedValueException when the required field is absent or the wrong type, correctly treating the row as permanently invalid rather than dispatching the wrong job.