mykholy / laravel-flowguard
Laravel idempotency, durable webhook inbox, transactional outbox, and explicit reconciliation for uncertain operations.
Requires
- php: ^8.3
- illuminate/database: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- psr/log: ^3.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^11.5 || ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.0.2
- v1.0.1
- v1.0.0
- v0.1.0-beta.3
- v0.1.0-beta.2
- v0.1.0-beta.1
- dev-docs/adoption-checklist-and-share-ready
- dev-docs/external-review-evidence-20261016
- dev-docs/v102-release-verification-20261015
- dev-fix/stripe-delivery-counter
- dev-release/v1.0.0
- dev-test/stripe-process-death-acceptance
- dev-fix/stripe-endive-beta-3
- dev-release/beta-2-stripe-pilot
- dev-test/checkout-demo-host
- dev-test/stable-large-worker-evidence
- dev-fix/public-beta-verification
- dev-release/v0.1.0-beta.1
- dev-release/distribution-readiness
- dev-fix/validate-reconciliation-reason-in-store
- dev-fix/doctor-foreign-key-integrity
- dev-docs/release-evidence-after-full-schema-restore
- dev-test/native-full-schema-recovery
- dev-security/evidence-reference-minimization
- dev-docs/accurate-dispatch-onboarding
- dev-test/freeze-consumer-api-contracts
- dev-fix/json-native-completion-metadata
- dev-ci/disposable-100-worker-probe
- dev-ci/php85-supported-runtime
- dev-docs/stable-review-handoff
- dev-test/strict-phpunit-notice-diagnostics
- dev-test/http-provider-acknowledgement-loss
- dev-feat/scoped-outbox-leases
- dev-docs/prestable-accuracy-audit
- dev-test/outbox-stress-evidence
- dev-fix/doctor-unique-index-audit
- dev-test/relational-restore-fidelity
- dev-fix/skip-locked-outbox-reservation
- dev-test/native-backup-restore-drill
- dev-test/controlled-outbox-stress
- dev-ci/clean-install-mysql-postgres
- dev-test/draft-public-api-contract
- dev-security/redact-owner-capabilities
- dev-docs/retention-and-archival-contract
- dev-test/outbox-upgrade-with-existing-data
- dev-feat/artisan-outbox-dispatch-once
- dev-fix/guarded-migration-rollbacks
- dev-ci/avoid-duplicate-branch-runs
- dev-test/tenant-authorization-boundary
- dev-feat/outbox-readonly-inspection
- dev-test/clean-laravel-install
- dev-perf/reproducible-outbox-benchmark
- dev-docs/developer-setup-and-project-status
- dev-docs/release-readiness-gates
- dev-feat/installation-diagnostics
- dev-fix/strict-unique-operation-claims
- dev-test/at-least-once-handoff
- dev-test/outbox-worker-crash-recovery
- dev-feat/outbox-dispatch-boundary
- dev-feat/durable-inbox-receipts
- dev-ci/laravel12-13-compatibility
- dev-feat/outbox-lease-lifecycle
- dev-feat/transactional-outbox-recording
- dev-feat/audited-manual-resolution
- dev-feat/authorized-evidence-journal
- dev-feat/reconciliation-inspection-contracts
- dev-feat/production-safety-foundation
This package is auto-updated.
Last update: 2026-10-16 17:21:05 UTC
README
Laravel FlowGuard
Durable operation ownership for requests, webhooks and background delivery.
A customer clicks Pay twice. A webhook arrives again. A worker dies after the provider accepted its request. FlowGuard gives your application durable identities, exclusive operation claims and explicit unknown outcomes so it can handle those failures deliberately.
Stable: v1.0.2. The public API and storage contract follow SemVer.
Install · Payment example · Getting started · Documentation · Adoption checklist · Runnable demo
What it helps you build
| Your failure scenario | FlowGuard provides | You supply |
|---|---|---|
| Two requests attempt the same payment or fulfillment | Scoped operation ownership and immutable payload fingerprints | Authorized tenant context and provider idempotency |
| A provider times out after possibly succeeding | Explicit reconciliation state and read-only evidence contracts | Provider lookup and outcome handling |
| A verified webhook arrives repeatedly | Durable inbox receipt and atomic outbox handoff | Signature verification and stable event identity |
| A database change commits but queue dispatch fails | Transactional outbox on the business connection | Atomic business write and event recording |
| A worker crashes after delivering a message | Expiring leases, fenced acknowledgement and bounded retries | Supervised publisher and destination deduplication |
Telescope, Horizon and Pulse help observe your application. FlowGuard coordinates ownership and durable handoff inside the business workflow.
Installation
composer require mykholy/laravel-flowguard:^1.0 php artisan migrate php artisan flowguard:doctor
Laravel discovers the service provider and loads the migrations automatically. The doctor checks local storage readiness. Follow the getting-started guide to choose an integration path.
| Dependency | Supported / verified versions |
|---|---|
| PHP | 8.3, 8.4, 8.5 |
| Laravel | 12, 13 |
| Database | SQLite, MySQL 8, PostgreSQL 16 |
| Package manager | Composer 2 |
To select a database connection, publish the configuration before migrating:
php artisan vendor:publish --tag=flowguard-config
Set FLOWGUARD_DB_CONNECTION to a configured Laravel connection. The default is
your application's default connection. Use durable transactional storage and the
same connection for business writes and outbox signals. Review
upgrades before changing an existing installation.
Payment example
The following is an application integration sketch. $gateway, the authorized
merchant, order and amount come from your application; FlowGuard does not bundle
a payment gateway.
use Mykholy\Flowguard\Flowguard; // Resolve the merchant from authenticated, authorized application context. $guard = app(Flowguard::class); $operation = $guard->begin( scope: 'payments.capture', idempotencyKey: $merchantId . ':' . $orderId, payload: ['order_id' => $orderId, 'amount_minor' => $amountMinor, 'currency' => 'EGP'], ); if (! $operation->acquired()) { // NEVER call the gateway from this branch. // Inspect $operation->operation->status and return an appropriate // API response (completed / already processing / manual reconciliation). return; } $claim = $operation->claim(); try { // The gateway must support its own idempotency key where available. $payment = $gateway->capture($orderId, $amountMinor, idempotencyKey: $merchantId . ':' . $orderId); } catch (\Throwable $e) { // An exception after dispatch may mean the provider already charged. // Use provider-specific semantics for errors known to occur before dispatch. $guard->requireReconciliation($claim, 'capture_outcome_unknown'); throw $e; } // Do not catch errors from complete() as if the provider capture failed. // Persist local business writes atomically where required before this call. // If finalization fails after capture, investigate without repeating the charge. $guard->complete($claim, ['provider_reference' => $payment->reference]);
The caller must ensure errors before dispatch and known vs unknown outcomes are handled
according to provider semantics. A provider can charge successfully even when local
completion fails. Do not treat failed complete() as grounds for retrying the charge.
See RFC-001 and threat model.
Webhooks and reliable delivery
Use only the primitives your workflow needs:
- Webhook inbox: accept an already verified event once per tenant identity and payload, with an atomic outbox signal.
- Transactional outbox: record a durable signal alongside the business write on the same connection.
- Publisher integration: bind your own
OutboxPublisher, then run an explicitly supervised delivery worker. - Reconciliation: inspect unknown outcomes; optional evidence journaling and manual classification require host policies.
There is no automatic publisher schedule. After binding your publisher, one bounded delivery attempt is available through:
php artisan flowguard:dispatch-once --lease=60 --retry=60 --max-attempts=10
Consumers must deduplicate by message identity. A worker can crash after remote acceptance and before local acknowledgement, causing redelivery. Configure the publisher's timeout shorter than its lease. See delivery semantics.
Diagnostics and operations
php artisan flowguard:doctor php artisan flowguard:stale --minutes=15 --limit=50 php artisan flowguard:outbox --max-attempts=10 --limit=50
These commands inspect state. A stale processing claim remains fenced; age never authorizes repeating a payment. Start with the operations runbook for incidents, retention for data handling and restore drills for recovery.
Documentation
| Start here | Read next |
|---|---|
| Getting started | Installation, operation claims and atomic event recording |
| Documentation index | Guides grouped by integration, operations and design |
| Public API | Supported calls, named parameters and compatibility rules |
| Checkout demo | Isolated Laravel host, signed webhooks and failure exercises |
| Verification evidence | Release checks, measured workloads and evidence boundaries |
Guarantees and integration boundaries
- Database uniqueness arbitrates claims; duplicate callers never receive another caller's owner capability. Changed payloads conflict instead of silently replaying.
- Unknown outcomes remain blocked. Processing claims have no automatic TTL reclaim.
- Outbox delivery is at least once. Exactly-once external effects are not guaranteed.
- Your application owns authentication, tenant isolation, signature verification, provider idempotency, timeouts, approval identities and retention.
- Store bounded safe metadata and opaque references; keep credentials and raw provider bodies out of package records.
The stable version commits to library compatibility. Independent human security and adopter reviews remain open. See security, tenant boundaries and release scope for the precise contract.
Development and contributions
composer install
composer validate --strict
composer test
composer analyse
composer format-check
php tests/smoke.php
node --test tools/check-preview-release.test.cjs
CI covers Laravel/PHP compatibility, independent SQL workers, archive-based installation, demo-host behavior and native restoration. See concurrency testing and contributing. Report private security issues through SECURITY.md.
MIT © 2026 Ahmed El-Kholy.
