# PUSHERY Documentation > Documentation for PUSHERY apps and packages — installation, configuration, > integration and reference for Laravel developers. Every page below is also available as Markdown at the same path without its trailing slash, followed by `.md` — `/pkg/page/` is mirrored at `/pkg/page.md`. The whole corpus as one document: https://docs.pushery.com/llms-full.txt ## Billing for Laravel Provider-neutral billing for Laravel: subscriptions, invoices, metered usage, dunning, tax and e-invoicing. Stripe-first, on provider-neutral contracts. - [Billing for Laravel](https://docs.pushery.com/billing-for-laravel/): Subscriptions, invoicing, tax and a multi-merchant surface for Laravel — start here. - [Choosing your setup](https://docs.pushery.com/billing-for-laravel/choosing-your-setup/): Which driver and which billing engine fit what you are selling. - [Choosing your mode](https://docs.pushery.com/billing-for-laravel/choosing-your-mode/): Which of the four operating modes your business model puts you in, and what each one configures. ### Single-seller - [Installation](https://docs.pushery.com/billing-for-laravel/single-seller/installation/): Getting from composer require to a working subscription. - [Configuration](https://docs.pushery.com/billing-for-laravel/single-seller/configuration/): How the configuration fits together, and which decisions it is asking you to make. - [Tiers and pricing](https://docs.pushery.com/billing-for-laravel/single-seller/tiers-and-pricing/): Defining what you sell, and keeping a price out of the reach of a request. - [Subscriptions](https://docs.pushery.com/billing-for-laravel/single-seller/subscriptions/): Plans, swaps, proration and trials — the recurring cycle end to end. - [Usage-based billing](https://docs.pushery.com/billing-for-laravel/single-seller/usage-based-billing/): Metering, quotas and prepaid units, with the races already handled. - [Invoices and e-invoicing](https://docs.pushery.com/billing-for-laravel/single-seller/invoices-and-e-invoicing/): Numbered invoices, EN 16931 e-invoicing, and what a correction may and may not change. - [Taxes](https://docs.pushery.com/billing-for-laravel/single-seller/taxes/): Choosing a tax mode, what each one computes, and how an exemption is stated. - [Dunning and suspension](https://docs.pushery.com/billing-for-laravel/single-seller/dunning-and-suspension/): What happens when a payment fails, step by step, and where access is cut off. - [The account hub](https://docs.pushery.com/billing-for-laravel/single-seller/account-hub/): The screens your customers manage their own subscription from. - [Webhooks](https://docs.pushery.com/billing-for-laravel/single-seller/webhooks/): Receiving provider events exactly once, and what each effect does. - [Admin and support](https://docs.pushery.com/billing-for-laravel/single-seller/admin-and-support/): Comping, refunding and canceling on a customer's behalf, on the record. - [Accounting and DATEV](https://docs.pushery.com/billing-for-laravel/single-seller/accounting-and-datev/): Exporting a period as a booking batch your accountant can import. - [Data protection](https://docs.pushery.com/billing-for-laravel/single-seller/data-protection/): What the package stores about a customer, and how to export or erase it. - [i18n and translations](https://docs.pushery.com/billing-for-laravel/single-seller/i18n-and-translations/): The shipped locales, and how to override any string the package says. ### Reference - [Configuration reference](https://docs.pushery.com/billing-for-laravel/reference/configuration/): Every configuration key and environment variable, with its shipped default. - [Command reference](https://docs.pushery.com/billing-for-laravel/reference/commands/): Every artisan command the package ships, what it does, and when to run it. - [Contract reference](https://docs.pushery.com/billing-for-laravel/reference/contracts/): The seams you bind your own implementations to, and the guarantees each one owes. - [Database reference](https://docs.pushery.com/billing-for-laravel/reference/database/): Every table the package creates, what it holds, and which columns are frozen. - [Event reference](https://docs.pushery.com/billing-for-laravel/reference/events/): Every domain event the package dispatches, and what has already happened when it fires. ### Guides - [Testing](https://docs.pushery.com/billing-for-laravel/guides/testing/): Testing an application that bills, without reaching a payment provider. - [Troubleshooting](https://docs.pushery.com/billing-for-laravel/guides/troubleshooting/): Every exception the package throws, what it is protecting, and what to do about it. - [Upgrading](https://docs.pushery.com/billing-for-laravel/guides/upgrading/): What each release changes, and which changes need action before you deploy. - [Migrating from Cashier](https://docs.pushery.com/billing-for-laravel/guides/migrating-from-cashier/): Moving an existing Cashier installation across without reissuing documents or losing history. - [Migrating from your own billing code](https://docs.pushery.com/billing-for-laravel/guides/migrating-from-custom-billing/): Replacing hand-rolled billing, and the order that keeps money correct while you do it. - [Selling works to consumers](https://docs.pushery.com/billing-for-laravel/guides/selling-works/): What the package tells you about a purchase, so you can label it correctly — and what it deliberately leaves to you. - [Mounting your own payment element](https://docs.pushery.com/billing-for-laravel/guides/mounting-your-own-payment-element/): Why the package ships no front-end JavaScript, and the seam for when you need the card fields inline. ### Marketplace - [The marketplace surface](https://docs.pushery.com/billing-for-laravel/marketplace/overview/): Routing money to merchants: postures, charge types, and what the documents must say. ### Compliance - [Invariants](https://docs.pushery.com/billing-for-laravel/compliance/invariants/): The properties this package refuses to break, and what enforces each one. - [Security](https://docs.pushery.com/billing-for-laravel/compliance/security/): The security boundaries the package enforces for you, and the ones that remain yours. - [Retention and erasure](https://docs.pushery.com/billing-for-laravel/compliance/retention-and-erasure/): How long each record is kept, why the windows differ, and what an erasure actually removes. ## Email Magic Link for Laravel Passwordless email authentication for Laravel — magic links and one-time codes — that works standalone or alongside Laravel Fortify. - [Email Magic Link for Laravel](https://docs.pushery.com/email-magic-link-for-laravel/): Passwordless email authentication for Laravel — magic links and one-time codes — that works standalone or alongside Laravel Fortify. - [Installation](https://docs.pushery.com/email-magic-link-for-laravel/installation/): Requirements, composer require, the installer command, the migration, the publish tags, and the queue worker the magic-link email needs. - [Quick start](https://docs.pushery.com/email-magic-link-for-laravel/quick-start/): The browser flow the package registers out of the box, the routes it adds, and why consuming a magic link costs one extra click. - [Configuration](https://docs.pushery.com/email-magic-link-for-laravel/configuration/): The config file end to end — the master switch, the three Fortify setups, token lifetimes, the invalid-link response, routing, rate limits, and the user interface. - [Security model](https://docs.pushery.com/email-magic-link-for-laravel/security-model/): Each concrete threat the package is designed against and the decision that addresses it — every row exercised by the test suite. ### Features - [Minting links and codes yourself](https://docs.pushery.com/email-magic-link-for-laravel/features/issuing-links-and-codes/): The Mint API issues a signed single-use link or a one-time code and hands it back without sending anything, so you can deliver it over SMS, chat, or your own transactional email. - [Multi-use links](https://docs.pushery.com/email-magic-link-for-laravel/features/multi-use-links/): Hand out a magic link that may be redeemed a bounded number of times, with the remaining-uses counter decremented atomically so concurrent redemptions can never exceed the limit. - [Passphrase-gated links](https://docs.pushery.com/email-magic-link-for-laravel/features/passphrase-gated-links/): Require a shared secret, delivered out of band, before a high-value magic link is consumed — a lightweight gate in front of the flow, never a replacement for two-factor authentication. - [One-time codes](https://docs.pushery.com/email-magic-link-for-laravel/features/one-time-codes/): Email a short code instead of a link, governed by a boot-time entropy guardrail that refuses to start when the keyspace is too small for the attempt lockout. - [The two-factor handoff](https://docs.pushery.com/email-magic-link-for-laravel/features/two-factor-handoff/): How a user with two-factor authentication enabled is handed to Fortify's own challenge without being logged in, the guard-alignment rule it depends on, and the trade-off it accepts. - [The resend guard](https://docs.pushery.com/email-magic-link-for-laravel/features/resend-guard/): An escalating cooldown and a rolling hourly cap layered on the fixed-window limiters, so a repeatedly clicked "send again" cannot flood an inbox — and a public service you can wrap around your own endpoints. - [Multiple guards](https://docs.pushery.com/email-magic-link-for-laravel/features/multiple-guards/): Let a request sign in to another guard — an admin guard alongside web — with an allowlist that keeps guards un-enumerable. - [The JSON contract](https://docs.pushery.com/email-magic-link-for-laravel/features/json-api/): Direct token exchange for first-party SPA and mobile clients — the stable status and error-code table, and how a two-factor challenge is signaled. - [Translations](https://docs.pushery.com/email-magic-link-for-laravel/features/translations/): Every user-facing string runs through Laravel's translator under the email-magic-link namespace; the bundled locales can be republished and reworded, and a new one added by copying a directory. - [The WireKit screens](https://docs.pushery.com/email-magic-link-for-laravel/features/wirekit-screens/): With pushery/wirekit installed the sign-in screens render with WireKit components automatically — how the layout wires design tokens, utility classes and behavior, and how to opt out. - [Invitations](https://docs.pushery.com/email-magic-link-for-laravel/features/invitations/): Invite somebody who has no account yet. The package issues, supersedes and spends the token; your application decides what accepting one means. ### Guides - [Authorizing a gated resource without login](https://docs.pushery.com/email-magic-link-for-laravel/guides/gated-resource-without-login/): Use the same single-use, hashed-at-rest token to authorize one specific resource — a one-time download or a gated view — without creating a session and without a serialized payload. - [Extension points](https://docs.pushery.com/email-magic-link-for-laravel/guides/extension-points/): Take over the post-verification flow, react to events, swap the collaborators, and gate requests with a CAPTCHA. - [Keeping the token table small](https://docs.pushery.com/email-magic-link-for-laravel/guides/token-cleanup/): Every request inserts a row and consumption only marks it consumed — schedule the bundled purge command to delete expired and consumed tokens. - [Running under multi-tenancy](https://docs.pushery.com/email-magic-link-for-laravel/guides/multi-tenancy/): Re-define the package's named rate limiters after a per-tenant cache swap, and give the routes the middleware that puts them on the tenant connection. ### Reference - [Configuration reference](https://docs.pushery.com/email-magic-link-for-laravel/reference/configuration/): Every key in config/email-magic-link.php with its default, its environment variable, and what it governs. - [Route reference](https://docs.pushery.com/email-magic-link-for-laravel/reference/routes/): Every route the package registers, with its name, middleware, rate limiter, and whether it changes state. - [Command reference](https://docs.pushery.com/email-magic-link-for-laravel/reference/commands/): The Artisan commands the package ships — the installer, the config doctor and the token purge — with their options and cadence. - [Event reference](https://docs.pushery.com/email-magic-link-for-laravel/reference/events/): The eight events the package dispatches, their payloads, and the ClaimFailure cases a failed consume attempt reports. - [Contract reference](https://docs.pushery.com/email-magic-link-for-laravel/reference/contracts/): The eleven interfaces under EmailMagicLink\Contracts — their signatures, what each one guarantees, and how to bind your own implementation. ## Legal Consent for Laravel Court-proof, versioned legal consent for Laravel: an append-only acceptance ledger, four notice modes, durable-medium delivery proof, and a re-consent gate. - [Legal Consent for Laravel](https://docs.pushery.com/legal-consent-for-laravel/): Court-proof, versioned legal consent for Laravel — an append-only acceptance ledger that renders and proves your legal texts instead of owning them. - [Installation](https://docs.pushery.com/legal-consent-for-laravel/installation/): Requirements, composer require, the publish tags, and the two groups that stay separate because publishing them unasked would destroy data. - [Quick start](https://docs.pushery.com/legal-consent-for-laravel/quick-start/): Write a legal text, publish a version, give a model a consent ledger, and enforce re-consent — the four steps that take a fresh install to a working gate. - [Recording consent](https://docs.pushery.com/legal-consent-for-laravel/recording-consent/): The four ways to write the consent ledger — the Fortify trait, the Registered event listener, the headless JSON API, and the interstitial for sign-ups that have no form — plus the registration checklist and the accept-time content-hash guard. - [Objections and terminations](https://docs.pushery.com/legal-consent-for-laravel/objections-and-terminations/): A change can be answered without accepting it — the Widerspruch and the free right to terminate each append their own ledger row and fire an event only your app can act on. - [Content sources](https://docs.pushery.com/legal-consent-for-laravel/content-sources/): Where a legal text comes from — Markdown files, the admin-maintained draft store, or your own class — and how to interpolate operator identity without breaking the proof. - [Managing legal texts](https://docs.pushery.com/legal-consent-for-laravel/managing-legal-texts/): The two opt-in, fail-closed admin screens, why a published row is frozen forever, and how to render the public legal page from the frozen bytes. - [User interface](https://docs.pushery.com/legal-consent-for-laravel/user-interface/): Three levels of UI — plain Blade stubs, opt-in Livewire components, a WireKit-native variant — the bundled translations, and the anti-dark-pattern rules every variant bakes in. - [Optional features](https://docs.pushery.com/legal-consent-for-laravel/optional-features/): Five capabilities that stay off until you switch them on — the tamper-evidence hash chain, the Art. 8 age gate, multi-tenancy scoping, the two-row double opt-in, and the document fragment a dialog can hold. - [Retention — turn it on, or nothing is ever deleted](https://docs.pushery.com/legal-consent-for-laravel/retention/): retention_after_end is a statement of policy, not an enforcement. The sweep that acts on it is the one scheduled task this package does not register for you. - [Upgrading](https://docs.pushery.com/legal-consent-for-laravel/upgrading/): Where the canonical upgrade guide lives, and the full detail of the backward-compatible 0.2.x to 0.3.0 bump. - [Testing your integration](https://docs.pushery.com/legal-consent-for-laravel/testing/): Consent::fake() swaps the manager for an in-memory double, so your tests can exercise consent screens, gates and register forms without migrating this package's tables into your test schema. ### Notice modes - [The four notice modes](https://docs.pushery.com/legal-consent-for-laravel/notice-modes/overview/): A change to a legal text is not one thing — the package models the four ways a change is communicated and enforced, so it gets exactly the notice the law requires and never more. - [Worked example — an info-only change](https://docs.pushery.com/legal-consent-for-laravel/notice-modes/information-only/): A payment-contract change your users only need to be told about — announced, recorded, effective on its date, and blocking nobody. - [Worked example — deemed consent](https://docs.pushery.com/legal-consent-for-laravel/notice-modes/deemed-consent/): A minor contract change where silence may bind — the objection window, the statutory period measured to the deadline, and the sweep that records silence as acceptance. - [Worked example — active re-consent](https://docs.pushery.com/legal-consent-for-laravel/notice-modes/active-re-consent/): A material core change still requires active agreement, with the full grace period and a hard gate at the deadline. - [The delivery proof](https://docs.pushery.com/legal-consent-for-laravel/notice-modes/delivery-proof/): Every pushed change notice writes an append-only legal_notices row — who was informed, when, on which medium, the exact text sent and its hash. - [Saying what changed](https://docs.pushery.com/legal-consent-for-laravel/notice-modes/change-descriptions/): A per-version, per-locale description of the change — the headline, the impact, and a typed list — frozen onto the version at publish and carried into every notice. ### Reference - [Configuration reference](https://docs.pushery.com/legal-consent-for-laravel/reference/configuration/): Every key in config/legal-consent.php you are likely to touch, its default, and what it decides. - [Command reference](https://docs.pushery.com/legal-consent-for-laravel/reference/commands/): Every Artisan command the package registers, what it does, and the full flag set of legal-consent:publish. ## Matomo Analytics for Laravel Privacy-first Matomo tracking for Laravel: client and server, single or batched, with AI-bot detection, tracking gates and fail-safe delivery that never blocks your app. - [Matomo Analytics for Laravel](https://docs.pushery.com/matomo-analytics-for-laravel/): Privacy-first Matomo tracking for Laravel — client and server, single or batched, with AI-bot detection, tracking gates and fail-safe delivery that never blocks a response. - [Installation](https://docs.pushery.com/matomo-analytics-for-laravel/installation/): Install the package, publish its config, point it at your Matomo instance with two environment variables, and verify connectivity from the command line. - [Configuration](https://docs.pushery.com/matomo-analytics-for-laravel/configuration/): How the published config file relates to the package defaults, which settings belong in .env, what each configuration block covers, and the one rule config caching imposes. - [Matomo Cloud](https://docs.pushery.com/matomo-analytics-for-laravel/matomo-cloud/): Cloud and self-hosted Matomo run through the same code path — only the host differs. The three Cloud-specific things worth knowing, and how to verify a Cloud setup end to end. ### Tracking - [Server-side tracking](https://docs.pushery.com/matomo-analytics-for-laravel/tracking/server-side/): Track from anywhere with the Matomo facade, record page views automatically with middleware, and understand how a visitor is identified without cookies. - [Client-side tracking](https://docs.pushery.com/matomo-analytics-for-laravel/tracking/client-side/): Render the Matomo JavaScript tracker with one Blade directive — cookieless, consent-aware, CSP-ready — or load a Tag Manager container instead. - [SPA and soft navigation](https://docs.pushery.com/matomo-analytics-for-laravel/tracking/spa-navigation/): Record a virtual page view on every client-side navigation — Livewire, Inertia or any History-based router — with an escape hatch for custom triggers. - [Page performance](https://docs.pushery.com/matomo-analytics-for-laravel/tracking/page-performance/): Matomo's native page-timing report fills itself on real page loads. The three settings that give you control, and how to feed it from a soft navigation or a purely server-side setup. - [Web Vitals](https://docs.pushery.com/matomo-analytics-for-laravel/tracking/web-vitals/): Capture Google's Core Web Vitals — LCP, CLS, INP — as Matomo events through a throttled server-side ingest route, with no third-party CDN. - [Ecommerce](https://docs.pushery.com/matomo-analytics-for-laravel/tracking/ecommerce/): Track product views, cart changes and completed orders server-side — fail-safe and gated like every other hit. - [Site search](https://docs.pushery.com/matomo-analytics-for-laravel/tracking/site-search/): Record what visitors search for on your own site — including the searches that returned nothing, which are the most valuable ones. - [Custom dimensions and content tracking](https://docs.pushery.com/matomo-analytics-for-laravel/tracking/custom-dimensions-and-content/): Attach your own fields to a hit, reach any raw Tracking-API parameter, and measure impressions and interactions on content blocks — client-side or server-side. - [Prefetched pages](https://docs.pushery.com/matomo-analytics-for-laravel/tracking/prefetch-beacon/): Speculation Rules prefetch a page before anybody opens it — how the package keeps that out of your page views, and how the page reports itself when the reader does click. ### Delivery - [Transmission modes](https://docs.pushery.com/matomo-analytics-for-laravel/delivery/transmission-modes/): Three ways hits leave your application — queue, sync and batch — switched with one environment variable, and what each one asks of your infrastructure. - [Scaling](https://docs.pushery.com/matomo-analytics-for-laravel/delivery/scaling/): Where the real ceiling is at high volume, how to measure your own numbers with the load simulator, and how the package composes with Matomo's QueuedTracking plugin. - [Reliability](https://docs.pushery.com/matomo-analytics-for-laravel/delivery/reliability/): Tracking never blocks a response and never throws into your app. What happens on a failure — retries, throttled alerting, poison batches and the dead-letter queue. ### Privacy and gating - [The tracking gate](https://docs.pushery.com/matomo-analytics-for-laravel/privacy/tracking-gate/): One decision point answers "is this request tracked?" — a fixed order of config-driven checks, with a hook for your own rule and an event for every exclusion. - [Bots and AI crawlers](https://docs.pushery.com/matomo-analytics-for-laravel/privacy/bots-and-ai-crawlers/): Bots and AI training crawlers are excluded by default. How the layered detection works, in what order, and how to reach exhaustive coverage with an optional backstop. - [AI assistants](https://docs.pushery.com/matomo-analytics-for-laravel/privacy/ai-assistants/): Matomo splits AI traffic three ways. Referrals from an assistant work with no configuration; training crawlers are excluded by default; on-demand fetchers need server-side telemetry, which this package sends itself. - [Consent and redaction](https://docs.pushery.com/matomo-analytics-for-laravel/privacy/consent-and-redaction/): Cookieless by default, Do-Not-Track honored server-side, URL redaction on out of the box, a publishable privacy-policy partial, and a first-party opt-out cookie the server can actually see. - [GDPR data-subject requests](https://docs.pushery.com/matomo-analytics-for-laravel/privacy/gdpr-requests/): Handle access and erasure requests through Matomo's PrivacyManager API — preview the match, export it, erase it, and keep an audit trail. ### Reporting - [Reporting overview](https://docs.pushery.com/matomo-analytics-for-laravel/reporting/overview/): Pull statistics back out of Matomo with the MatomoReports facade — curated helpers, one round-trip bulk requests, date-aware caching, and an error convention that never throws. - [Queries and segments](https://docs.pushery.com/matomo-analytics-for-laravel/reporting/queries-and-segments/): Build a filtered, segmented report query fluently, compose segments with a builder, and register named segments in config. - [Premium plugin reports](https://docs.pushery.com/matomo-analytics-for-laravel/reporting/premium-plugins/): Thin read adapters for Matomo's licensed premium plugins, which degrade gracefully to null when the plugin is not installed. ### Guides - [Release annotations](https://docs.pushery.com/matomo-analytics-for-laravel/guides/release-annotations/): Mark deployments on your Matomo reports timeline with the free Annotations plugin — from code or from a deploy pipeline, and safe to run unconditionally. - [Laravel Octane](https://docs.pushery.com/matomo-analytics-for-laravel/guides/octane/): Octane-safe by construction — every request-stateful service is scoped, and a test drives Octane's between-request reset to prove no state leaks. - [Testing](https://docs.pushery.com/matomo-analytics-for-laravel/guides/testing/): Swap in a fake for each facade and assert what would have been tracked, requested, erased or annotated — without a Matomo instance anywhere near your test suite. - [Troubleshooting](https://docs.pushery.com/matomo-analytics-for-laravel/guides/troubleshooting/): Symptom-first answers for the ways a correctly installed package still produces no data — and the one event that tells you why. - [Upgrading](https://docs.pushery.com/matomo-analytics-for-laravel/guides/upgrading/): What a version bump can and cannot change, why you never have to re-publish the config file, and the three things worth checking after an update. ### Reference - [Configuration reference](https://docs.pushery.com/matomo-analytics-for-laravel/reference/configuration/): Every configuration key the package reads, its environment variable and its shipped default — the authoritative list, since a published config file need not contain them all. - [Command reference](https://docs.pushery.com/matomo-analytics-for-laravel/reference/commands/): Every Artisan command the package registers, its options, what it does and when to run it. - [Events](https://docs.pushery.com/matomo-analytics-for-laravel/reference/events/): Six events let you hook tracking into your own pipelines — what each carries, when it fires, and the one that answers "why is my traffic missing?". - [Contracts](https://docs.pushery.com/matomo-analytics-for-laravel/reference/contracts/): The interfaces the package binds and you can rebind — the extension seams, what each one guarantees, and which are worth replacing. - [Database tables](https://docs.pushery.com/matomo-analytics-for-laravel/reference/database/): The tables this package creates, their columns, when they are written, and how to opt out of them entirely. ## Polyslug for Laravel Polymorphic, multilingual routable identity for Eloquent: pretty URLs that are safe to expose, safe to rename, and correct across languages. - [Polyslug for Laravel](https://docs.pushery.com/polyslug-for-laravel/): Polymorphic, multilingual routable identity for Eloquent — pretty URLs that are safe to expose, safe to rename, and correct across languages. - [Installation](https://docs.pushery.com/polyslug-for-laravel/installation/): Requirements, composer require, the migration, and the optional publishable files. - [Quick start](https://docs.pushery.com/polyslug-for-laravel/quick-start/): Mark a model sluggable, point a route at it, and watch old URLs redirect themselves. - [How it works](https://docs.pushery.com/polyslug-for-laravel/how-it-works/): The two-part route key, why resolution decodes the id instead of matching the slug, and what makes a slug write concurrency-safe. - [Recipes](https://docs.pushery.com/polyslug-for-laravel/recipes/): Twelve app shapes wired end to end. Each one combines features documented under Features — nothing here is a separate mechanism. ### Features - [Sluggable models](https://docs.pushery.com/polyslug-for-laravel/features/sluggable-models/): The attribute, the trait and the interface that make an Eloquent model routable — plus every option and the methods you get. - [Identity encoders](https://docs.pushery.com/polyslug-for-laravel/features/identity-encoders/): The pluggable token that stands in for the primary key — what each shipped encoder reveals, how to write your own, and how to migrate between them. - [Self-healing routes](https://docs.pushery.com/polyslug-for-laravel/features/self-healing-routes/): The canonical-redirect middleware, the Route::polyslug macro, and exactly which requests get redirected. - [Access control](https://docs.pushery.com/polyslug-for-laravel/features/access-control/): polyslugResolveQuery constrains which rows a slug may resolve to; polyslugIsRoutable keeps models out of hreflang sets and sitemaps. - [Slug-only URLs](https://docs.pushery.com/polyslug-for-laravel/features/slug-only-urls/): idLess drops the id suffix so the URL is the slug alone — and what Polyslug does to keep that safe. - [Token-only URLs](https://docs.pushery.com/polyslug-for-laravel/features/token-only-urls/): slugless drops the slug so the URL is the token alone — and how to choose how long and how random that token is. - [Multilingual slugs and hreflang](https://docs.pushery.com/polyslug-for-laravel/features/multilingual-and-hreflang/): One slug per locale, a reciprocal hreflang set built from the same resolver as the canonical URL, and locale-aware redirects. - [Polymorphic routing](https://docs.pushery.com/polyslug-for-laravel/features/polymorphic-routing/): One {type}/{polyslug} route that serves every content type, backed by a config registry. - [Uniqueness and scope](https://docs.pushery.com/polyslug-for-laravel/features/uniqueness-and-scope/): What a slug is unique against, how to scope it to a tenant or a parent, and how to let records share a slug outright. - [Transliteration and Unicode](https://docs.pushery.com/polyslug-for-laravel/features/transliteration-and-unicode/): The two ASCII transliteration profiles, the native Unicode mode for non-Latin markets, and what happens when a title has no sluggable characters. - [History, events and immutability](https://docs.pushery.com/polyslug-for-laravel/features/history-events-and-immutability/): The SlugChanged and SlugRedirected events, frozen slugs, and reading a model's superseded slugs. - [Gone and superseded content](https://docs.pushery.com/polyslug-for-laravel/features/gone-and-superseded/): Return 410 for permanently removed content and 301 to a successor for content that moved — instead of a soft-404 that loses its ranking. - [Nested (hierarchical) slugs](https://docs.pushery.com/polyslug-for-laravel/features/nested-slugs/): Compose ancestor slugs into the URL path — computed on read, so renaming or reparenting an ancestor needs no cascade. - [The URL resolver](https://docs.pushery.com/polyslug-for-laravel/features/url-resolver/): The one class you have to write yourself — Polyslug knows a model's slug, but only your application knows the route it lives on. Bind it once and short links, sitemaps and canonical tags all work. - [Sitemaps](https://docs.pushery.com/polyslug-for-laravel/features/sitemaps/): A streaming XML sitemap with reciprocal hreflang alternates, built from a resolver you bind. - [Short links](https://docs.pushery.com/polyslug-for-laravel/features/short-links/): A stable /go token per model and locale that always redirects to the current canonical URL — so a printed or QR-coded link survives every rename. - [Backfilling existing rows](https://docs.pushery.com/polyslug-for-laravel/features/backfilling/): Generate slugs for rows that predate Polyslug — inline or as chunked queued jobs, safe to run repeatedly. - [laravel/head integration](https://docs.pushery.com/polyslug-for-laravel/features/laravel-head/): Let a Polyslug model write its own canonical URL, hreflang set, Open Graph locales and robots directive into laravel/head — the four head facts only Polyslug knows. ### Recipes - [Multi-tenant SaaS](https://docs.pushery.com/polyslug-for-laravel/recipes/multi-tenant-saas/): Per-tenant slugs with no cross-tenant leak — scope for uniqueness, a resolution gate for isolation. - [News and magazine](https://docs.pushery.com/polyslug-for-laravel/recipes/news-and-magazine/): Multilingual, SEO-first publishing — native-script headlines, reciprocal hreflang, and a generated sitemap. - [E-commerce](https://docs.pushery.com/polyslug-for-laravel/recipes/e-commerce/): Nested category paths that reorganize without a cascade — /electronics/phones/iphone. - [Documentation and knowledge base](https://docs.pushery.com/polyslug-for-laravel/recipes/knowledge-base/): Clean slug-only URLs with no id suffix — and the guarantees that make dropping the id safe. - [Enumeration-sensitive data](https://docs.pushery.com/polyslug-for-laravel/recipes/enumeration-sensitive-data/): Unguessable URLs for integer-keyed tables where row count, growth and ordering must not be inferable. - [Sharing and QR codes](https://docs.pushery.com/polyslug-for-laravel/recipes/sharing-and-qr-codes/): A stable short link per model that keeps following the canonical URL, however often the slug changes. - [Social and user-generated content](https://docs.pushery.com/polyslug-for-laravel/recipes/social-and-ugc/): Let many posts share a title without a -2 suffix — the encoded id already disambiguates them. - [Marketplace](https://docs.pushery.com/polyslug-for-laravel/recipes/marketplace/): Per-seller slugs where only live listings resolve — and drafts stay out of both the index and the sitemap. - [Headless CMS](https://docs.pushery.com/polyslug-for-laravel/recipes/headless-cms/): One route for every content type, driven by a registry instead of a route file that grows with the model list. - [Government and enterprise](https://docs.pushery.com/polyslug-for-laravel/recipes/government-and-enterprise/): Frozen slugs for citable documents, 410 Gone for repealed records, and 301 to the amendment that replaced them. - [Events and ticketing](https://docs.pushery.com/polyslug-for-laravel/recipes/events-and-ticketing/): QR short links printed on a ticket that keep working after the event is renamed or rescheduled. - [Real estate and geo](https://docs.pushery.com/polyslug-for-laravel/recipes/real-estate-and-geo/): Location-composed paths where identical listing titles are fine across cities — /berlin/mitte/loft-am-park. ### Reference - [Configuration](https://docs.pushery.com/polyslug-for-laravel/reference/configuration/): Every key in config/polyslug.php, its default, and what it changes. - [Attribute options](https://docs.pushery.com/polyslug-for-laravel/reference/attribute-options/): Every #[Polyslug] constructor parameter with its type, default and effect. - [Model API](https://docs.pushery.com/polyslug-for-laravel/reference/model-api/): Every method the HasPolyslug trait adds and every hook the Sluggable interface lets you override. - [Commands](https://docs.pushery.com/polyslug-for-laravel/reference/commands/): The four Artisan commands the package registers, with every option. - [Events](https://docs.pushery.com/polyslug-for-laravel/reference/events/): The three events Polyslug dispatches, with their payloads and when they fire. - [Contracts](https://docs.pushery.com/polyslug-for-laravel/reference/contracts/): Every interface you can implement or bind, and what each one promises. - [Exceptions](https://docs.pushery.com/polyslug-for-laravel/reference/exceptions/): The five exceptions the package throws, what each one means, and what to change. - [Database](https://docs.pushery.com/polyslug-for-laravel/reference/database/): The three tables the package creates, the indexes that carry its guarantees, and how those differ per engine. ### Guides - [Testing](https://docs.pushery.com/polyslug-for-laravel/guides/testing/): Assert slug behavior in your own suite with the InteractsWithPolyslug trait. - [Diagnostics](https://docs.pushery.com/polyslug-for-laravel/guides/diagnostics/): What polyslug:doctor checks, what each failure means, and when to run it. - [Troubleshooting](https://docs.pushery.com/polyslug-for-laravel/guides/troubleshooting/): Common symptoms, and the check that identifies each one. ## SQLens for Laravel Database safety toolkit for Laravel on PostgreSQL and MySQL: lint pending migrations for lock and downtime risk, audit the live schema, and classify drift, security and deploy risk. - [SQLens for Laravel — documentation](https://docs.pushery.com/sqlens-for-laravel/): A database safety toolkit for Laravel on PostgreSQL and MySQL: what SQLens checks, and what it refuses to guess. - [Integration guide](https://docs.pushery.com/sqlens-for-laravel/integration-guide/): From `composer require` to a deploy gate that stops a bad migration — the whole path, in the order you walk it. - [The lint suite](https://docs.pushery.com/sqlens-for-laravel/lint/): How the lint suite reads your migrations, what each strictness level adds, and how to wire it into CI. - [The audit suite: auditing a live database](https://docs.pushery.com/sqlens-for-laravel/audit/): What `sqlens:audit` reads on a real database, and the four things it makes you state rather than guess: which instance, what stays ignored, which tenant, and what the reading role may see. - [The security suite](https://docs.pushery.com/sqlens-for-laravel/security-suite/): `sqlens:security` — roles, grants, transport, RLS and unencrypted columns, gated by severity rather than by strictness level. - [Formatting your SQL](https://docs.pushery.com/sqlens-for-laravel/format/): One house style for the SQL in your migrations, a --check mode a CI step can act on, three backends behind one seam, and the four things the formatter will never do to a statement. - [Runtime guardrails](https://docs.pushery.com/sqlens-for-laravel/guard/): What sqlens.guard watches in a running application, why it is off unless you name a profile, why it logs instead of throwing in production, and what it deliberately cannot see. - [The `analyse` suite: reading the code that hands SQL to the database](https://docs.pushery.com/sqlens-for-laravel/analyse/): What the PHPStan extension checks, how it sits beside Larastan and phpstan-dba, and why raw SQL needs a written reason rather than a type check. - [Checking the environment](https://docs.pushery.com/sqlens-for-laravel/doctor/): `sqlens:doctor` reports what SQLens found — tool versions, server versions, and what an absence costs you. - [Agent context files](https://docs.pushery.com/sqlens-for-laravel/agent-rules/): `sqlens:agent-rules` writes the project's active rule set into its coding agents' context files, so an agent knows the rule before it writes the migration. - [Capture modes: pretend and shadow](https://docs.pushery.com/sqlens-for-laravel/capture-modes/): Pretend and shadow: the two ways SQLens obtains the SQL a migration will run, and what each one structurally cannot see. - [Shadow mode: setup, required privileges, and topologies](https://docs.pushery.com/sqlens-for-laravel/shadow-mode/): Shadow mode is the one part of SQLens that asks for more than read-only access. - [The canonical form](https://docs.pushery.com/sqlens-for-laravel/canonical-form/): The normalized form every rule reads, and why two spellings of one statement must not produce two answers. - [The catalog reader: what it reads, what it never touches](https://docs.pushery.com/sqlens-for-laravel/catalog-reader/): What SQLens reads from a live database, what it never touches, the privileges it needs, and which skips are normal on managed databases. - [The audit role: what SQLens needs to read, and what it deliberately does not](https://docs.pushery.com/sqlens-for-laravel/audit-role/): A least-privilege account for `sqlens:audit`, on PostgreSQL and MySQL — measured against real servers, with the exact checks each profile can and cannot answer. - [Read/write splits: which server did the audit read?](https://docs.pushery.com/sqlens-for-laravel/read-write-split/): Why SQLens refuses to run against a split connection nobody pinned a host on, how to pin one, and what it can and cannot confirm afterwards. - [The strictness levels — how much a project wants to be told](https://docs.pushery.com/sqlens-for-laravel/strictness-levels/): Levels are an appetite, not a severity. Level 6 is a conversation, not a defect list. - [Understanding `undetermined`](https://docs.pushery.com/sqlens-for-laravel/understanding-undetermined/): Why a check that could not run is reported as undetermined rather than as a pass, and what to do with one. - [Escalation thresholds: why a finding gets louder on a big table](https://docs.pushery.com/sqlens-for-laravel/escalation-thresholds/): The size thresholds that raise a deploy finding's severity, where each number comes from, why they are heuristics rather than measurements, and how a project overrides them. - [Scope and limits](https://docs.pushery.com/sqlens-for-laravel/scope-and-limits/): What SQLens checks today, what it deliberately does not, and how it tells you the difference instead of staying quiet. - [The public contracts](https://docs.pushery.com/sqlens-for-laravel/contracts/): The exit codes, the JSON envelope, and the baseline format — what each promises, and across which versions. - [Rules — how an id is built, and how to silence one](https://docs.pushery.com/sqlens-for-laravel/rules/): Every finding names a rule id. This is what the parts of that id mean, and the five layers that can hide it. - [The public API, and where it stops](https://docs.pushery.com/sqlens-for-laravel/public-api/): Four promises, one boundary, and the rule that decides whether a command is one of them. - [The remediation payload](https://docs.pushery.com/sqlens-for-laravel/remediation-schema/): The machine-readable fix material that rides beside a finding — every field, what a consumer must check, and the line SQLens does not cross. - [Documentation URL conventions](https://docs.pushery.com/sqlens-for-laravel/conventions/): The naming, level and severity conventions every rule follows, so an id tells you what a finding is. - [The deploy recipe: wire the gate so a failure actually stops the migration](https://docs.pushery.com/sqlens-for-laravel/deploy-recipe/): The three-command chain around migrate --force, the shell options that make a failing gate stop the deploy rather than warn about it, and what each exit code means to the script. - [What sqlens:predeploy needs to be allowed to do — and what it never asks for](https://docs.pushery.com/sqlens-for-laravel/predeploy-permissions/): The read-only role the deploy gate runs as, on PostgreSQL and MySQL, which checks go undetermined without which privilege, and the four things this package will never request. - [The deploy readers: what they need to see, and what a partial answer means](https://docs.pushery.com/sqlens-for-laravel/deploy-readers/): Which privileges the statistics and activity readers need on PostgreSQL and MySQL, what each state view does when the privilege is missing — measured against real servers — and how to read an estimate. - [Post-deploy verification](https://docs.pushery.com/sqlens-for-laravel/postdeploy-verification/): What sqlens:postdeploy reads after `migrate --force`, what it deliberately is not, the exit codes a deploy script acts on, and the privileges it needs. - [sqlens:drift — what the database holds against what the migrations describe](https://docs.pushery.com/sqlens-for-laravel/drift/): The comparison no .sql tool can make: the live catalog against a real replay of the migration state. Why it reports rather than blocks, how to accept the differences you meant to keep, and the one thing an exclude file must never be able to hide. - [Drift detection rollout](https://docs.pushery.com/sqlens-for-laravel/drift-rollout/): How to bring a grown production database under drift control in three steps, why skipping the first one is what kills the feature, and when it is safe to start failing builds. - [The migration debt account](https://docs.pushery.com/sqlens-for-laravel/debt-ledger/): Some safe patterns are two-step, and the second step is the one that gets forgotten. The ledger is the account of those open ends. - [MCP server: connecting an agent to the same engine](https://docs.pushery.com/sqlens-for-laravel/mcp-server/): Run SQLens as a Model Context Protocol server so an agent checks migrations against the same rules your pipeline does. - [Advisory data](https://docs.pushery.com/sqlens-for-laravel/advisory-data/): Where the end-of-life dates come from, why nothing is fetched unless you ask, and how to keep them current. - [Keeping the online-DDL matrix current](https://docs.pushery.com/sqlens-for-laravel/online-ddl-matrix/): The per-operation matrix behind every downtime claim, and the conditions that make an operation online or blocking. - [Squawk parity](https://docs.pushery.com/sqlens-for-laravel/squawk-parity/): Every rule Squawk has, and what SQLens does about it — covered, covered differently, reported as the tool's own, or deliberately not covered. - [Outages on the record — what actually went wrong, and what this catches](https://docs.pushery.com/sqlens-for-laravel/incidents/): Documented production incidents caused by schema change, the rules that bear on each one, and the one in this list SQLens would not have prevented. ### Drivers - [PostgreSQL driver](https://docs.pushery.com/sqlens-for-laravel/drivers/pgsql/): What the PostgreSQL driver reads, the version floor it assumes, and the engine facts its rules are built on. - [MySQL driver](https://docs.pushery.com/sqlens-for-laravel/drivers/mysql/): What the MySQL driver reads, the version floor it assumes, and the engine facts its rules are built on. - [When no driver resolves](https://docs.pushery.com/sqlens-for-laravel/drivers/unsupported/): What happens when SQLens meets a database engine it has no driver for, and why that is a named result rather than a crash. ### Rules - [CAP.L0.DOWN_FAILED — the down() leg raised an error](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-down-failed/): While --roundtrip replayed the migration in a throwaway database, its down() raised an error. - [CAP.L0.DOWN_NOT_INVERTIBLE — down() did not restore what up() changed](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-down-not-invertible/): The roundtrip replayed up, then down, then up again inside a throwaway database, and the second up failed. - [CAP.L0.MIGRATE_ERROR — Error during the real shadow migrate](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-migrate-error/): The migration failed while it was being run for real against the throwaway shadow database. - [CAP.L0.NOT_CAPTURABLE — No capturable SQL](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-not-capturable/): The capture ran to completion but produced no SQL to lint. - [CAP.L0.PRETEND_ERROR — Error during the pretend run](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-pretend-error/): The migration threw while it was being captured under pretend, so its SQL could not be linted. - [CAP.L0.UNDETERMINED_CAPTURE — Capture could not conclude](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-undetermined-capture/): Without this finding such a migration would contribute nothing to the run and vanish from it — the silent green the tool forbids. - [CAP.PRESCAN.INDIRECT_CALL — Indirect call from a migration](https://docs.pushery.com/sqlens-for-laravel/rules/cap-prescan-indirect-call/): This migration reaches its effect through your own code — (new Backfill)->run(), app(Importer::class)->handle() — the most common way around the side-effect catalog. - [CAP.PRESCAN.INTROSPECTION_GUARD — Schema introspection guard](https://docs.pushery.com/sqlens-for-laravel/rules/cap-prescan-introspection-guard/): This migration's DDL sits behind a schema-introspection guard — if (! - [CAP.PRESCAN.RESULT_DEPENDENT — Result-dependent migration](https://docs.pushery.com/sqlens-for-laravel/rules/cap-prescan-result-dependent/): This migration's SQL depends on what a query answers. - [CAP.PRESCAN.SIDE_EFFECT — Side effect in a migration](https://docs.pushery.com/sqlens-for-laravel/rules/cap-prescan-side-effect/): This migration would reach outside the database if it ran — a notification, an HTTP request, a queued job, a mail, a cache write. - [GEN.L1.DOWN_MORE_DESTRUCTIVE — A rollback that destroys more than the migration built](https://docs.pushery.com/sqlens-for-laravel/rules/gen-l1-down-more-destructive/): Laravel rolls back by calling down(), and infers nothing from up(). - [GEN.L4.DOWN_MISSING — A migration that cannot be rolled back](https://docs.pushery.com/sqlens-for-laravel/rules/gen-l4-down-missing/): Laravel rolls back by calling down(), and nothing else. - [MY.L1.DROP_COLUMN — Dropping a column on MySQL commits itself](https://docs.pushery.com/sqlens-for-laravel/rules/my-l1-drop-column/): DROP COLUMN deletes the column and its data, and on MySQL no transaction takes it back. - [MY.L1.DROP_TABLE — Dropping a table on MySQL commits itself](https://docs.pushery.com/sqlens-for-laravel/rules/my-l1-drop-table/): DROP TABLE is DDL on MySQL, so no transaction takes it back — it is final the instant it runs. - [MY.L1.TRUNCATE — TRUNCATE in a migration, where no transaction takes it back](https://docs.pushery.com/sqlens-for-laravel/rules/my-l1-truncate/): TRUNCATE in a migration's up() empties the table with no confirmation and no predicate to limit it. - [MY.L2.COPY_ALTER_CHARSET — Character-set conversion that copies the table](https://docs.pushery.com/sqlens-for-laravel/rules/my-l2-copy-alter-charset/): ALTER TABLE … CONVERT TO CHARACTER SET … re-encodes every value in every string column. - [MY.L4.DROP_WITHOUT_DEPLOY_WINDOW — A drop breaks whoever is still reading](https://docs.pushery.com/sqlens-for-laravel/rules/my-l4-drop-without-deploy-window/): Dropping a table or column breaks any old application version that still references it. - [MY.L2.COPY_ALTER_TYPE — Column redefinition that rebuilds the table](https://docs.pushery.com/sqlens-for-laravel/rules/my-l2-copy-alter-type/): $table->…->change() compiles to ALTER TABLE … MODIFY, and MySQL's MODIFY takes the column's whole definition rather than a delta. - [MY.L2.FK_TARGET_NON_UNIQUE — Foreign key onto a target no unique key covers](https://docs.pushery.com/sqlens-for-laravel/rules/my-l2-fk-target-non-unique/): So this is a deploy that breaks today, on the version this package supports, in the configuration a user gets without doing anything. - [MY.L2.NO_PRIMARY_KEY — InnoDB table left without a primary key](https://docs.pushery.com/sqlens-for-laravel/rules/my-l2-no-primary-key/): On MySQL a table without a primary key is not a matter of schema taste. - [MY.L3.ALGORITHM_LOCK_UNEXPRESSIBLE — An operation MySQL will not run online, written the one way that cannot say so](https://docs.pushery.com/sqlens-for-laravel/rules/my-l3-algorithm-lock-unexpressible/): Laravel's MySQL grammar has no way to emit ALGORITHM= or LOCK=. - [MY.L3.MIXED_DDL_DML_NOT_ATOMIC — A migration that changes the schema and then writes data](https://docs.pushery.com/sqlens-for-laravel/rules/my-l3-mixed-ddl-dml-not-atomic/): On MySQL, a migration that changes the schema and then writes data is not one operation, whatever you assumed while writing it. - [MY.L3.MISSING_LOCK_WAIT_TIMEOUT — A migration that waits for a metadata lock without a bound](https://docs.pushery.com/sqlens-for-laravel/rules/my-l3-missing-lock-wait-timeout/): MySQL waits a year by default for a metadata lock, and grants those locks in order — so a stuck schema change also stops the reads queued behind it. - [MY.L4.ENUM_CHANGE — Changing an ENUM's member list](https://docs.pushery.com/sqlens-for-laravel/rules/my-l4-enum-change/): MySQL stores an ENUM value as its ordinal position in the member list, not as the string. - [MY.L6.EXPLICIT_DEFAULTS_FOR_TIMESTAMP_OFF — legacy TIMESTAMP behavior the migration never asked for](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-explicit-defaults-for-timestamp-off/): A server with explicit_defaults_for_timestamp off rewrites TIMESTAMP columns on update and turns a written NULL into the current time. - [MY.L6.TIME_ZONE_NOT_UTC — a server whose clock is the host's, not a decision](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-time-zone-not-utc/): A MySQL server on time_zone = SYSTEM converts timestamps using the host's zone, so the same schema behaves differently on a different machine. - [PG.L5.FK_NO_INDEX — Foreign key whose referencing column no index covers](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l5-fk-no-index/): PostgreSQL indexes the referenced side of a foreign key and not the referencing one, so every delete on the parent scans the child table. - [MY.L6.INNODB_ROW_FORMAT_NOT_DYNAMIC — the 767-byte ceiling that aborts a deploy halfway](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-innodb-row-format-not-dynamic/): A server creating InnoDB tables as COMPACT gives them a 767-byte index key limit, so the first unique string index fails mid-deploy. - [PG.L5.NO_PRIMARY_KEY — Table with no primary key and nothing that can stand in for one](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l5-no-primary-key/): A table PostgreSQL cannot identify a row in cannot be replicated, audited, or recovered row by row. - [MY.L6.CHARACTER_SET_SERVER_NOT_UTF8MB4 — a default charset that quietly cannot hold your data](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-character-set-server-not-utf8mb4/): Objects created without an explicit CHARACTER SET take the server's, and on latin1 or utf8mb3 they silently alter values MySQL cannot represent. - [MY.L5.SQL_MODE_NOT_STRICT — a truncated value the server called a success](https://docs.pushery.com/sqlens-for-laravel/rules/my-l5-sql-mode-not-strict/): A MySQL server whose sql_mode omits STRICT_TRANS_TABLES, so a truncated value the server called a success. - [MY.L5.SQL_MODE_DIVISION_BY_ZERO_SILENT — a division by zero that answers NULL instead of raising](https://docs.pushery.com/sqlens-for-laravel/rules/my-l5-sql-mode-division-by-zero-silent/): A MySQL server whose sql_mode omits ERROR_FOR_DIVISION_BY_ZERO, so a division by zero that answers NULL instead of raising. - [MY.L6.SQL_MODE_ENGINE_SUBSTITUTION — a table that quietly got a different storage engine](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-sql-mode-engine-substitution/): A MySQL server whose sql_mode omits NO_ENGINE_SUBSTITUTION, so a table that quietly got a different storage engine. - [MY.L3.EXCHANGE_PARTITION_CLAUSE_IGNORED — An ALGORITHM or LOCK clause MySQL parses and discards](https://docs.pushery.com/sqlens-for-laravel/rules/my-l3-exchange-partition-clause-ignored/): Every other ALTER TABLE checks the clause and refuses a contradiction. EXCHANGE PARTITION takes it and does nothing. - [MY.L6.SQL_MODE_LOOSE_GROUP_BY — a GROUP BY that returns a value from a row nobody chose](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-sql-mode-loose-group-by/): A MySQL server whose sql_mode omits ONLY_FULL_GROUP_BY, so a GROUP BY that returns a value from a row nobody chose. - [MY.L6.DEFAULT_STORAGE_ENGINE_NOT_INNODB — a table with no transactions and no foreign keys](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-default-storage-engine-not-innodb/): A server whose default storage engine is not InnoDB creates tables without transactions, foreign keys or crash recovery when a migration does not name one. - [MY.L6.TIME_ZONE_TABLES_EMPTY — a named zone the server cannot resolve](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-time-zone-tables-empty/): A MySQL server on a named time zone whose mysql.time_zone_name holds no rows cannot resolve it, and CONVERT_TZ answers NULL instead of raising. - [MY.L6.LOWER_CASE_TABLE_NAMES_RISK — the development-against-production split you cannot switch off](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-lower-case-table-names-risk/): lower_case_table_names is fixed when a server is initialized, so a mixed-case migration can pass locally and fail in production. - [PG.L5.COLLATION_VERSION_MISMATCH — indexes sorted by rules the server no longer uses](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l5-collation-version-mismatch/): A collation whose installed version has moved since the indexes were built can make an equality lookup miss a row that is present. - [PG.L1.DROP_SCHEMA — Dropping a schema is every table in it at once](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l1-drop-schema/): DROP SCHEMA is not scoped to one object, and CASCADE reaches past the schema entirely. - [PG.L1.DROP_TABLE — Dropping a table is a deploy you cannot undo](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l1-drop-table/): DROP TABLE takes the rows with it, and CASCADE takes whatever was built on them. - [PG.L1.DROP_COLUMN — The old app version is still selecting it](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l1-drop-column/): Dropping a column breaks every running release that still reads it, and the data is not coming back. - [PG.L1.TRUNCATE — Emptying a table locks out every reader](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l1-truncate/): TRUNCATE is not a fast DELETE — it takes a lock nobody can read through, and there is no WHERE clause to have forgotten. - [PG.L2.INDEX_NOT_CONCURRENT — Building an index blocks every write until it finishes](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l2-index-not-concurrent/): CREATE INDEX holds a lock against writes for the whole build. CONCURRENTLY does not — and cannot run in a transaction. - [PG.L2.DROP_INDEX_NOT_CONCURRENT — Dropping an index locks the table, not the index](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l2-drop-index-not-concurrent/): DROP INDEX takes an ACCESS EXCLUSIVE lock on the table for the duration. CONCURRENTLY does not. - [PG.L2.CONSTRAINT_NOT_VALIDATED — Adding a constraint scans every row under a lock](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l2-constraint-not-validated/): ADD CONSTRAINT verifies the whole table before it returns. NOT VALID plus VALIDATE splits that into two cheap halves. - [PG.L2.SET_NOT_NULL_SCAN — Proving no row is null costs a full scan](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l2-set-not-null-scan/): SET NOT NULL scans the table under its lock. A validated CHECK constraint lets PostgreSQL skip that scan entirely. - [PG.L2.TYPE_CHANGE_REWRITE — A type change can rewrite the entire table](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l2-type-change-rewrite/): Unless the new type is binary-coercible from the old one, ALTER COLUMN TYPE rewrites every row under a lock. - [PG.L3.CONCURRENTLY_IN_TRANSACTION — CONCURRENTLY cannot run inside Laravel's transaction](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l3-concurrently-in-transaction/): PostgreSQL rejects a concurrent index build inside a transaction block, and Laravel opens one for every migration. - [PG.L3.MISSING_LOCK_TIMEOUT — Without it, the migration waits and everything queues behind it](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l3-missing-lock-timeout/): A lock_timeout aborts your migration instead of stalling the application. That trade is almost always the right one. - [PG.L3.MISSING_STATEMENT_TIMEOUT — Bounding how long a step may hold what it took](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l3-missing-statement-timeout/): lock_timeout bounds the wait. statement_timeout bounds the hold. A migration needs both. - [PG.L3.RISKY_OPS_SINGLE_TX — Locks are held until the whole transaction commits](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l3-risky-ops-single-tx/): Two locking operations in one migration hold both locks until the last one finishes, not until each one does. - [PG.L4.ENUM_ADD_VALUE — Adding an enum value is a one-way change](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l4-enum-add-value/): PostgreSQL has no ALTER TYPE … DROP VALUE, so down() cannot undo it. A CHECK-constrained column can. - [PG.L4.ENUM_VALUE_REMOVED — Renaming an enum value rewrites the meaning of stored data](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l4-enum-value-removed/): Rows already hold the old name. Renaming the value changes what they mean without touching them. - [PG.L4.CHECK_ENUM_CHANGE — Changing a Laravel enum() column re-checks every row](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l4-check-enum-change/): A Laravel enum() is a varchar with a CHECK. Swapping the constraint validates the whole table under a lock and leaves no rolling-deploy window. - [PG.L4.TYPE_NARROWING — Making a column smaller can lose what is in it](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l4-type-narrowing/): Narrowing asks the server to fit every stored value into a smaller type. It errors, or it truncates. - [PG.L4.DROP_WITHOUT_DEPLOY_WINDOW — The previous release is still running](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l4-drop-without-deploy-window/): A drop takes effect on commit. Every instance still serving the old release starts failing at that moment. - [PG.L4.CONSTRAINT_VALIDATION_PENDING — A constraint was added NOT VALID and never validated](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l4-constraint-validation-pending/): NOT VALID is the right first step, and only the first. Until VALIDATE CONSTRAINT runs, the constraint does not hold for the rows that were already there. - [PG.L6.TIMEZONE_NOT_UTC — a server whose day boundary is somewhere else](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l6-timezone-not-utc/): The instant is stored correctly either way. What moves is every date cast, every date_trunc and every "today" filter. - [PG.L6.STANDARD_CONFORMING_STRINGS_OFF — the same migration builds two different databases](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l6-standard-conforming-strings-off/): With it off, a backslash in an ordinary literal escapes the next character. Nothing raises; the row simply holds something else. - [PG.L6.DEFAULT_TRANSACTION_ISOLATION_DRIFT — a new error class your code has no reason to handle](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l6-default-transaction-isolation-drift/): Raising the server default changes what every transaction does, including the ones the framework opens for itself. - [PG.L6.DEFAULT_TRANSACTION_READ_ONLY — the write fails before anything can reason about it](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l6-default-transaction-read-only/): A transaction that does not say READ WRITE refuses writes, and the error reads like a permissions problem without being one. - [PG.L7.DATA_CHECKSUMS_DISABLED — corruption from below is returned as data](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l7-data-checksums-disabled/): With checksums on, a damaged page is detected on read. Without them it is handed to your application as a value. - [GEN.L1.DML_WITHOUT_WHERE — the predicate you meant to type](https://docs.pushery.com/sqlens-for-laravel/rules/gen-l1-dml-without-where/): An UPDATE or DELETE with no WHERE touches every row. The statement is valid, which is the whole problem. - [GEN.L3.DML_ON_SCHEMA_CHANGED_TABLE — the backfill runs while the schema lock is still held](https://docs.pushery.com/sqlens-for-laravel/rules/gen-l3-dml-on-schema-changed-table/): Change the schema in one migration and fill the data in the next. Together, the lock lasts for both. - [LINT.NO_ACTIVE_RULES — the scope admitted no rule, so nothing was checked](https://docs.pushery.com/sqlens-for-laravel/rules/lint-no-active-rules/): A run that checked nothing and a run that found nothing produce the same silence. This finding tells them apart. - [LINT.SKIPPED — the run ended without linting anything](https://docs.pushery.com/sqlens-for-laravel/rules/lint-skipped/): Nothing was pending, nothing could be captured, or the path matched no migration. Each reason is named. - [LINT.SKIPPED.MISSING_TOOL — an optional external tool is not installed](https://docs.pushery.com/sqlens-for-laravel/rules/lint-skipped-missing-tool/): The rules that needed it did not run. They are named, and the run says so rather than reporting silence. - [LINT.VERSION_SKEW — the pinned version and the server disagree](https://docs.pushery.com/sqlens-for-laravel/rules/lint-version-skew/): Version-gated rules were applied for the version you pinned, not the one that answered. - [LINT.VERSION_PIN_UNREADABLE — the pin could not be read, and nothing was assumed instead](https://docs.pushery.com/sqlens-for-laravel/rules/lint-version-pin-unreadable/): A malformed assume_server_version is not silently ignored. Version-gated rules report undetermined rather than guessing a version. - [AUDIT.CATALOG.NOT_COMPARED — read completely, and outside a comparison on purpose](https://docs.pushery.com/sqlens-for-laravel/rules/audit-catalog-not-compared/): An index the audit read and understood, which a rule deliberately does not compare with something else. Reported, and not undetermined. - [AUDIT.CATALOG.UNREAD — the catalog could not be read, so nothing was judged](https://docs.pushery.com/sqlens-for-laravel/rules/audit-catalog-unread/): An audit that read nothing and a database with nothing wrong produce the same empty report. This is the difference. - [LINT.NO_MIGRATIONS_READ — the run judged no migration at all](https://docs.pushery.com/sqlens-for-laravel/rules/lint-no-migrations-read/): `--path` lints the pending migrations, and nothing is pending on a database that has been migrated. This finding is what stops that from reading as a clean run. - [PG.L6.PK_UUID_V4 — Random UUID primary key where PostgreSQL 18 offers a time-ordered one](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l6-pk-uuid-v4/): A v4 UUID primary key scatters inserts across the index; PostgreSQL 18's uuidv7() keeps the uniqueness and adds order. - [PG.L6.TIMESTAMP_NO_TZ — Timestamp column with no time zone](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l6-timestamp-no-tz/): A zone-less timestamp stores the digits it was handed and no clock to read them against, and the failure is silent. - [PG.L6.JSON_NOT_JSONB — A json column where jsonb is almost always meant](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l6-json-not-jsonb/): json re-parses the document on every read and cannot be indexed; jsonb parses once and can. The exception is when the exact bytes matter. - [PG.L6.PK_NOT_BIGINT — An integer primary key too narrow to grow into](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l6-pk-not-bigint/): The key runs out with no warning, and the fix falls due exactly when it is most expensive. - [MY.L6.PK_NOT_BIGINT — An integer primary key too narrow to grow into](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-pk-not-bigint/): The key runs out with no warning, and the fix falls due exactly when it is most expensive. - [PG.L6.SERIAL_NOT_IDENTITY — A key generated by serial rather than by an identity column](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l6-serial-not-identity/): serial is a macro, not a type. Its three pieces can drift apart; an identity column keeps them together. - [MY.L6.CHARSET_NOT_UTF8MB4 — Text stored under something narrower than utf8mb4](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-charset-not-utf8mb4/): latin1 announces itself; utf8mb3 is the one that looks solved and silently drops every emoji. - [MY.L6.COLLATION_LEGACY — A utf8mb4 object still sorting by a pre-8.0 collation](https://docs.pushery.com/sqlens-for-laravel/rules/my-l6-collation-legacy/): utf8mb4_general_ci does not just sort differently — for several languages it sorts wrongly. - [MY.L5.COLLATION_MIXED — A join across a collation boundary, which costs an index](https://docs.pushery.com/sqlens-for-laravel/rules/my-l5-collation-mixed/): MySQL converts one side at runtime, and the converted column stops being answerable from its index. - [PG.L5.FK_NULLABLE_IN_UNIQUE — A unique key that does not hold, because one of its foreign keys may be NULL](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l5-fk-nullable-in-unique/): UNIQUE is one of the few words in SQL people believe without checking. Over a nullable column it constrains nothing. - [MY.L5.FK_NULLABLE_IN_UNIQUE — A unique key that does not hold, because one of its foreign keys may be NULL](https://docs.pushery.com/sqlens-for-laravel/rules/my-l5-fk-nullable-in-unique/): MySQL counts two NULLs as different values, and offers no mode that says otherwise. - [PG.L5.FLOAT_MONEY — Money in a floating-point column](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l5-float-money/): A float cannot represent 0.10 exactly. The cent goes missing months later, with no way back to the column. - [MY.L5.FLOAT_MONEY — Money in a floating-point column](https://docs.pushery.com/sqlens-for-laravel/rules/my-l5-float-money/): A float cannot represent 0.10 exactly. The cent goes missing months later, with no way back to the column. - [PG.L5.MONEY_TYPE — The type called money is not the one you want](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l5-money-type/): Its value depends on a server setting, so a restore onto another locale reads the same characters as a different number. - [PG.L7.INDEX_REDUNDANT — An index whose work another index already does](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l7-index-redundant/): It answers nothing the wider index cannot, and is still written on every insert. - [MY.L7.INDEX_REDUNDANT — An index whose work another index already does](https://docs.pushery.com/sqlens-for-laravel/rules/my-l7-index-redundant/): It answers nothing the wider index cannot, and is still written on every insert. - [PG.L7.INDEX_UNUSED — An index nobody has read, as far as resettable counters can say](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l7-index-unused/): The counter is the easy half. The window it covers decides whether the number means anything. - [MY.L7.INDEX_UNUSED — An index nobody has read, on an engine that cannot say for how long](https://docs.pushery.com/sqlens-for-laravel/rules/my-l7-index-unused/): performance_schema answers how often; nothing answers since when. That gap decides the rule. - [PG.L7.UNBATCHED_MASS_DML — A backfill that nothing bounds](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l7-unbatched-mass-dml/): A data write in a migration that runs as one statement over however many rows the table happens to hold. - [MY.L7.UNBATCHED_MASS_DML — A backfill that nothing bounds](https://docs.pushery.com/sqlens-for-laravel/rules/my-l7-unbatched-mass-dml/): A data write in a migration that runs as one statement over however many rows the table happens to hold. - [CAP.L0.NO_ACTIVE_RULES — nothing was checked, and that is not a clean result](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-no-active-rules/): A level and category combination that admits no rule produces the same empty report as a healthy database. This is the difference. - [CAP.L0.INSTANCE_AMBIGUOUS — more than one connection could be the one to audit](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-instance-ambiguous/): An audit describes one database. When the configuration leaves several candidates and nothing chose, the run stops rather than picking. - [CAP.L0.AMBIGUOUS_READ_HOSTS — the connection offers several read hosts and nothing chose](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-ambiguous-read-hosts/): Laravel picks a read host at random. An audit that accepted that would describe a different server on different runs. - [CAP.L0.UNOFFERED_HOST — the named host is not one this connection configures](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-unoffered-host/): A host pin that the configuration does not offer is refused, never dialed. Auditing a server the project never configured is worse than not running. - [CAP.L0.PINNED_HOST_DIVERGED — the server that answered is not the one that was pinned](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-pinned-host-diverged/): A pin that did not take effect makes every finding a statement about a database nobody asked for, and it looks exactly like a real report. - [CAP.L0.PINNED_HOST_UNVERIFIED — the pin could not be checked against the server](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-pinned-host-unverified/): The pin may be right. Nobody can say so, and a report that quietly assumed it would be asserting something it never confirmed. - [CAP.L0.INSTANCE_DIVERGENCE — the server is not the one the configuration describes](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-instance-divergence/): The configuration says one thing and the server says another. SQLens cannot know which is right; it can refuse to let one be read as the other. - [CAP.L0.CONNECTION_POOLED — the session is multiplexed, so instance-wide facts cannot be trusted](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-connection-pooled/): A transaction pooler hands your session to a different backend between statements. Anything an audit concludes about "this instance" stops holding. - [CAP.L0.SERVER_UNREACHABLE — the connection could not be opened, so nothing was audited](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-server-unreachable/): An empty report because the run never reached a database is not an empty report because the database is in good order. - [CAP.L0.SETTINGS_UNREADABLE — the server would not report its own configuration](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-settings-unreadable/): Every server-baseline rule then has nothing to judge, and a rule with no subject reports nothing — which is what a healthy server also produces. - [CAP.L0.RULE_WITHHELD_BY_VERSION — a rule was not applied because of the server version](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-rule-withheld-by-version/): A rule set that shrinks against an older server produces a shorter report in which every finding is still true and nothing says a check was skipped. - [CAP.L0.INSTANCE_SCOPE_UNANSWERABLE — this instance cannot answer what the rule asks](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-instance-scope-unanswerable/): A rule scoped to the write path reads perfectly well on a replica. That is exactly the trap: the value it returns describes the replica. - [CAP.L0.INVALID_IGNORE_LIST — the ignore list names something that is not a rule](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-invalid-ignore-list/): A pattern matching nothing looks exactly like a pattern whose findings are gone. That is the same output for months. - [CAP.L0.ORPHANED_IGNORE — an ignore entry that no longer silences anything](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-orphaned-ignore/): A suppression list nobody prunes stops describing what a project accepts and becomes a list of things it once did. - [CAP.L0.NO_BASELINE_TO_IGNORE — `--ignore-baseline` was passed and there is no baseline](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-no-baseline-to-ignore/): A flag that silently does nothing teaches a reader that it did something. This says it did not. - [CAP.L0.TENANCY_NOT_DECLARED — this project looks multi-tenant and has not said so](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-tenancy-not-declared/): An audit across N tenant databases is not one statement. A report about whichever tenant was default reads exactly like a report about the application. - [CAP.L0.TENANCY_REFERENCE_MISSING — tenancy is `explicit` and no reference tenant is named](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-tenancy-reference-missing/): The project answered the question and stopped halfway. The run needs to know which tenant the report is about. - [CAP.L0.ASSUMED_VERSION_SKEW — the pinned version and the real server disagree](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-assumed-version-skew/): A pin makes a lint run reproducible. When the server it is compared against is a different version, findings are true about a database nobody deploys to. - [LINT.SERVER_BELOW_FLOOR — the version this run reasons from is below the supported floor](https://docs.pushery.com/sqlens-for-laravel/rules/lint-server-below-floor/): The findings were still produced, and they may be wrong in both directions. - [CAP.L0.SERVER_BELOW_FLOOR — the instance that answered is below the supported floor](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-server-below-floor/): The audit ran and reported, but its verdicts may not describe this server. - [CAP.L0.UNSUPPORTED_ENGINE — the engine that answered is not the one the driver names](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-unsupported-engine/): The audit stopped without checking anything, and that is the honest answer. - [SEC.PRIV.GRANT_PUBLIC — A privilege granted to PUBLIC](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-public/): PUBLIC is every role that exists and every role that ever will, so a grant to it cannot be reviewed by looking at who has access. - [CAP.L0.MISSING_TOOL — an amplifier was registered and did not answer](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-missing-tool/): An optional external tool could not run, so the checks it brings did not happen. Reported rather than skipped, because a smaller run must never look like a clean one. - [SEC.PRIV.GRANT_PUBLIC_IN_MIGRATION — A migration grants a privilege to PUBLIC](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-public-in-migration/): The catalog rule finds this after it has been applied. This one finds it in the migration, which is the last moment it is still a decision rather than a state. - [SEC.PRIV.ROLE_SUPERUSER — The account is a superuser, or one SET ROLE away](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-role-superuser/): Superuser is not a strong permission — it is the absence of permission checks. - [SEC.PRIV.GRANT_EXCESSIVE_IN_MIGRATION — A migration grants everything, or the right to grant it on](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-excessive-in-migration/): Two shapes that are permanent and invisible once they reach the catalog: every privilege on an object, and the option to hand the privilege to somebody else. - [SEC.PRIV.ROLE_CREATEROLE — The account may create roles](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-role-createrole/): It can mint login accounts and administer them — narrower than it used to be, and still a way to keep access. - [SEC.PRIV.ROUTINE_DEFINER_NO_PATH_IN_MIGRATION — A definer routine that was never told where to look](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-routine-definer-no-path-in-migration/): The routine runs as its owner while its names resolve against the caller. That is a privilege escalation with no injection and no bug in the function. - [SEC.PRIV.GRANT_SCOPE_BROAD_IN_MIGRATION — A grant whose scope is a wildcard](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-scope-broad-in-migration/): `ON *.*` reaches every database on the server; `ON db.*` reaches every table in one — including the tables added after this migration ran. - [SEC.PRIV.ROLE_BYPASSRLS — Row-level security does not apply to this account](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-role-bypassrls/): The policies are correct and beside the point for an account holding BYPASSRLS. - [SEC.PRIV.GRANT_ADMIN_IN_MIGRATION — A migration grants an administrative privilege](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-admin-in-migration/): FILE, PROCESS, RELOAD, SHUTDOWN, CREATE USER and the dynamic SUPER successors govern the server, not the data. An application account needs none of them. - [SEC.PRIV.RUNTIME_DDL — The runtime role may change the schema](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-runtime-ddl/): An injection that reaches a role with CREATE can leave a table, a function or a trigger behind. - [SEC.PRIV.CONNECTIONS_UNSEPARATED — One connection serves requests and deploys migrations](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-connections-unseparated/): The runtime role holds whatever DDL the migrations need, so an injection inherits it. - [SEC.PRIV.GRANT_WILDCARD_HOST_IN_MIGRATION — A grantee reachable from anywhere](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-wildcard-host-in-migration/): In MySQL the host half of an account is an access control, not a label. `'app'@'%'` is the same credentials with that control removed. - [SEC.RLS.DISABLED — A tenant table without row-level security](https://docs.pushery.com/sqlens-for-laravel/rules/sec-rls-disabled/): The table you listed as holding tenant data is readable in full by anything with SELECT. - [SEC.AUTH.PASSWORD_LITERAL_IN_MIGRATION — A password written into a migration](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-password-literal-in-migration/): The credential is now in version control, in every clone, and in the reflog after somebody removes it in a later commit. - [SEC.RLS.POLICY_ALWAYS_TRUE — A policy that lets every row through](https://docs.pushery.com/sqlens-for-laravel/rules/sec-rls-policy-always-true/): Row-level security is on, the table has a policy, and the separation still does not exist. - [SEC.RLS.CHECK_ALWAYS_TRUE — Reads are separated, writes are not](https://docs.pushery.com/sqlens-for-laravel/rules/sec-rls-check-always-true/): A tenant sees only its own rows and can write a row belonging to anyone. - [SEC.RLS.NO_POLICY — Row-level security is on and there is no policy](https://docs.pushery.com/sqlens-for-laravel/rules/sec-rls-no-policy/): Nothing is exposed; the table is simply unreadable for everyone but its owner. - [SEC.RLS.NOT_FORCED — Your policies do not apply to your own connection](https://docs.pushery.com/sqlens-for-laravel/rules/sec-rls-not-forced/): The application connects as the role that owns its tables, and owners are exempt. - [SEC.RLS.OWNER_UNRESTRICTED — The table's owner is exempt from its policies](https://docs.pushery.com/sqlens-for-laravel/rules/sec-rls-owner-unrestricted/): Your application path is restricted; migrations and console sessions are not. - [SEC.SKIPPED.* — Part of the security reading was refused](https://docs.pushery.com/sqlens-for-laravel/rules/sec-skipped/): On a managed database this is the ordinary state, and it is stated rather than hidden. - [SEC.AUTH.HBA_TRUST — A network line that asks for no password](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-hba-trust/): A `host` line in pg_hba.conf authenticates with `trust`, so anyone who can reach the server is whoever they say they are. - [SEC.AUTH.HBA_TRUST_LOCAL — Unix-socket connections without authentication](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-hba-trust-local/): A `local` line authenticates with `trust`, so OS access to the host is database access as any role. - [SEC.AUTH.HBA_CLEARTEXT — The password crosses the wire as typed](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-hba-cleartext/): The `password` auth method sends the credential unhashed and unchallenged. - [SEC.AUTH.HBA_MD5 — md5 authentication, whose verifier is the password](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-hba-md5/): The md5 method stores `md5(password || rolname)`, which is crackable offline and usable as-is. - [SEC.AUTH.HBA_OPEN_CIDR — The line accepts the whole address space](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-hba-open-cidr/): A host line matching 0.0.0.0/0 or ::/0 makes every other weakness reachable from anywhere. - [SEC.AUTH.HBA_PARSE_ERROR — A line the server could not parse](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-hba-parse-error/): PostgreSQL rejected a line of pg_hba.conf, so the restriction it was written to impose is not in force. - [SEC.PRIV.ROUTINE_DEFINER_MUTABLE_PATH — EXECUTE means "run code as the owner"](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-routine-definer-mutable-path/): A SECURITY DEFINER routine that does not pin its search_path lets its caller substitute the functions it calls. - [SEC.PRIV.ROUTINE_DEFINER — A routine that runs as its owner, correctly](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-routine-definer/): Nothing is wrong here. It is on the report because EXECUTE on this routine is a loan of the owner's rights. - [SEC.PRIV.ROUTINE_DEFINER_UNSAFE_PATH — a pinned search_path is not automatically a safe one](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-routine-definer-unsafe-path/): A SECURITY DEFINER routine whose pinned search_path names a schema somebody else can write to, or places pg_temp anywhere but last. - [SEC.AUTH.ROLE_DEPRECATED_PASSWORD_HASH — The account's password is stored under a retired verifier](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-role-deprecated-password-hash/): PostgreSQL calls it md5, MySQL calls it mysql_native_password, and MySQL 9.0 removes it outright. - [SEC.AUTH.ROLE_DEPRECATED_PASSWORD_HASH_LOCKED — A locked account still carrying the retired verifier](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-role-deprecated-password-hash-locked/): Locking an account is not fixing it — unlocking is one statement, and it comes back as it left. - [LINT.INVALID_CONFIG_REFERENCE — Your baseline names a rule that does not exist](https://docs.pushery.com/sqlens-for-laravel/rules/lint-invalid-config-reference/): A baseline entry for an unknown rule suppresses nothing while looking like a decision that still holds. - [LINT.DEBT.UNRECORDED — this run owes a debt the ledger has never heard of](https://docs.pushery.com/sqlens-for-laravel/rules/lint-debt-unrecorded/): An open end the account does not know about. Reported, never recorded: writing is a decision somebody makes. - [SEC.AUTH.ROLE_NO_PASSWORD — The account can log in and has no password at all](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-role-no-password/): A MySQL account on a password plugin with nothing stored under it. Reach the port, know the name, you are in. - [LINT.DEBT.STALE_ENTRY — the ledger holds a debt this run no longer owes](https://docs.pushery.com/sqlens-for-laravel/rules/lint-debt-stale-entry/): The project settled a debt and the file has not caught up. Bookkeeping, not a defect. - [SEC.AUTH.ROLE_NO_PASSWORD_LOCKED — A locked account with nothing behind the lock](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-role-no-password-locked/): Unlocking is one statement, and what comes back is an account anyone who knows its name can log into. - [LINT.DEBT.ACKNOWLEDGED_GONE — a debt somebody chose to carry has stopped being detected](https://docs.pushery.com/sqlens-for-laravel/rules/lint-debt-acknowledged-gone/): Deliberately not the same as a stale entry: an acknowledgment carries a written argument, and that is the thing that would be lost. - [SEC.AUTH.ROLE_WILDCARD_HOST — The account may connect from any host](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-role-wildcard-host/): The host half of a MySQL account name is an access control, and `%` matches every address. - [LINT.DEBT.LEDGER_UNREADABLE — the account exists and this build cannot act on it](https://docs.pushery.com/sqlens-for-laravel/rules/lint-debt-ledger-unreadable/): Undetermined, never an empty account: a file this build cannot read must not be reported as a project with no debts. - [SEC.AUTH.ROLE_WILDCARD_HOST_PRIVILEGED — Reachable from anywhere, and able to do anything](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-role-wildcard-host-privileged/): Either half is ordinary. Together they leave one guessed credential between the network and everything. - [LINT.DEBT.NOT_RECORDABLE — a recording run was asked for from a view that cannot support one](https://docs.pushery.com/sqlens-for-laravel/rules/lint-debt-not-recordable/): --debt=record needs the full pending set. One migration cannot tell an open debt from a settled one. - [SEC.AUTH.ROLE_ANONYMOUS — An account with no name is an account for anyone](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-role-anonymous/): An empty MySQL user name does not mean unused. It matches any name the server does not otherwise know. - [LINT.DEBT.ACKNOWLEDGMENT_EXPIRED — the decision to carry this debt was due for review](https://docs.pushery.com/sqlens-for-laravel/rules/lint-debt-acknowledgment-expired/): review_at is what keeps an acknowledgment from becoming permanent. This is it doing its job. - [SEC.AUTH.ROLE_ANONYMOUS_NO_PASSWORD — No name required, and no password either](https://docs.pushery.com/sqlens-for-laravel/rules/sec-auth-role-anonymous-no-password/): The two worst account states at once, and the shortest way into a server that exists. - [DEBT.STILL_OPEN — the account records this debt and the catalog still shows it](https://docs.pushery.com/sqlens-for-laravel/rules/debt-still-open/): An open end nobody finished, with its age attached. This is what the debt account is for. - [SEC.PRIV.GRANT_OPTION — The account can hand its access on](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-option/): WITH GRANT OPTION makes every other limit on the server voluntary. - [DEBT.RESOLVED — a recorded debt the catalog shows as settled](https://docs.pushery.com/sqlens-for-laravel/rules/debt-resolved/): Somebody finished it. Reported here and removed in the repository, never from a deploy server. - [SEC.PRIV.GRANT_OPTION_STRUCTURAL — It can hand on the power to change the schema](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-option-structural/): Not who may read what exists — who may decide what exists. - [DEBT.OBJECT_NOT_FOUND — the recorded object is not in the catalog](https://docs.pushery.com/sqlens-for-laravel/rules/debt-object-not-found/): Not 'settled'. A dropped table, a schema outside this run's scope and a missing privilege all look exactly like this. - [DEBT.UNRECORDED — a debt the database carries and the account has never heard of](https://docs.pushery.com/sqlens-for-laravel/rules/debt-unrecorded/): The oldest debt in a project is usually this one: a constraint that predates the tool, reported on every run with no age and no way to acknowledge it. - [SEC.PRIV.GRANT_ALL — An account holds every privilege the engine has](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-all/): There is no privilege escalation left to perform against an account that already has everything. - [SEC.PRIV.GRANT_SERVER_ADMIN — The account administers the server, not a database](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-server-admin/): And it does not need SUPER to do it — MySQL 8 split those powers across a family of dynamic privileges. - [DEBT.LEDGER_MISSING — the account was expected on this machine and is not there](https://docs.pushery.com/sqlens-for-laravel/rules/debt-ledger-missing/): The one place where a missing file must not be read as an empty account. - [SEC.PRIV.GRANT_FILE — An account can read and write files as the server](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-file/): The privilege that turns a SQL injection into a problem on the host, not in the database. - [DEBT.LEDGER_UNREADABLE — the account is present and this build cannot act on it](https://docs.pushery.com/sqlens-for-laravel/rules/debt-ledger-unreadable/): Its own finding, separate from a missing one: an absent file and a corrupt file send you to two different places. - [SEC.PRIV.GRANT_PROCESS — An account can read every other session's statements](https://docs.pushery.com/sqlens-for-laravel/rules/sec-priv-grant-process/): The leak is the statement text, not the session list: anything interpolated rather than bound travels in it. - [SEC.CFG.SECURE_FILE_PRIV — File I/O has no limit, and the value that says so looks like nothing](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-secure-file-priv/): The empty value is the dangerous one. MySQL writes the switched-off state as the string NULL. - [SEC.CFG.LOCAL_INFILE — The server may ask the client for a file](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-local-infile/): The direction is the opposite of what the statement name suggests: the server names the path. - [SEC.CFG.GENERAL_LOG — Every statement is being written down, verbatim](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-general-log/): The log does not redact. Passwords and tokens go in as they were sent. - [SEC.CFG.GENERAL_LOG_PERSONAL_DATA — Row values are being written to the query log](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-general-log-personal-data/): Every WHERE email = … with its value, in a file that outlives the row an erasure request removed. - [SEC.CFG.REQUIRE_SECURE_TRANSPORT — Nothing enforces the encryption the server already offers](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-require-secure-transport/): Not "encryption is off". A connection that skips it is accepted like any other. - [SEC.CFG.TLS_DISABLED — The server does not offer TLS at all](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-tls-disabled/): Not "encryption is optional" — no connection to this server can be encrypted, including the one you checked. - [SEC.CFG.PASSWORD_ENCRYPTION — New passwords are stored with a deprecated hash](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-password-encryption/): The one md5 finding that reports a server which is clean today — and will not stay that way. - [SEC.CFG.STATEMENT_LOGGING_SECRETS — Passwords and tokens are going into the server log](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-statement-logging-secrets/): CREATE ROLE … LOGIN PASSWORD is logged verbatim. PostgreSQL does not rewrite it. - [SEC.CFG.LOCAL_INFILE_FILE_GRANT — The capability and an account that can use it](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-local-infile-file-grant/): Two settings that are harmless apart and a route together — reported only when the grant tables could actually be read. - [SEC.CFG.PATCH_EOL — The server runs a release series that is out of support](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-patch-eol/): Nothing is broken today. The server has simply left the list that receives the fix for whatever is found next. - [SEC.SKIPPED.NOTHING_CHECKED — The security run examined nothing](https://docs.pushery.com/sqlens-for-laravel/rules/sec-skipped-nothing-checked/): Not a clean result. A suite that checked nothing has to say so, or its silence reads like a healthy database. - [SEC.CFG.STATEMENT_LOGGING — Statements and their values are written to the server log](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-statement-logging/): The same setting is good practice on a laptop and a disclosure in production, so this rule asks where it is looking first. - [SEC.INJ.RAW_SQL_WITHOUT_REASON — Raw SQL that nobody wrote a reason for](https://docs.pushery.com/sqlens-for-laravel/rules/sec-inj-raw-sql-without-reason/): A policy rule, not a vulnerability check. It never reads your query; it asks whether reaching for raw SQL was a decision somebody stated. - [SEC.INJ.RAW_INTERPOLATION — A runtime value was built into the statement instead of bound to it](https://docs.pushery.com/sqlens-for-laravel/rules/sec-inj-raw-interpolation/): The property that decides whether an injection is possible at all — reported without ever reading your query. - [SEC.INJ.RAW_SQL_REASON_STALE — a written reason that no longer covers any raw SQL](https://docs.pushery.com/sqlens-for-laravel/rules/sec-inj-raw-sql-reason-stale/): The expiry direction. An exemption that outlives its reason reads for years as a decision somebody weighed, and the next reader trusts it. - [SEC.INJ.DYNAMIC_IDENTIFIER — A column or sort direction is coming from the request](https://docs.pushery.com/sqlens-for-laravel/rules/sec-inj-dynamic-identifier/): An identifier is part of the statement, not a value, so it cannot be bound. The only fix is an allowlist — and the rule recognizes the ones you already write. - [SEC.PII.UNENCRYPTED_COLUMN — A column that looks like personal data is stored in the clear](https://docs.pushery.com/sqlens-for-laravel/rules/sec-pii-unencrypted-column/): The column name matches the privacy dictionary and no Eloquent model casts it to an encrypted type. - [DEPLOY.CONTEXT.READ_ONLY_TARGET — The instance this deploy points at will not accept writes](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-context-read-only-target/): What the check reads on PostgreSQL and on MySQL, why a standby and a deliberately read-only primary are named separately, and what to do with either. - [DEPLOY.CONTEXT.SESSION_DEFENSE_NOT_APPLIED — The timeouts this run set on its own session are not in force](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-context-session-defense-not-applied/): What the check reads back from its own session, why a `SET` that returned no error can still stop applying, and which connection to point the preflight at. - [DEPLOY.CONTEXT.SETTING — The settings this migration is about to run under, read instead of assumed](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-context-setting/): The four settings each engine reads right before `migrate --force`, why an ordinary everyday value becomes a finding at that moment, and what each one asks you to decide. - [DEPLOY.CONTEXT.VERSION_SKEW — the version CI checked against is not the server this deploy meets](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-context-version-skew/): What the check compares, what each of its answers means for the deploy, and which decision each one asks you to make. - [DEPLOY.LEGACY.CONSTRAINT_NOT_VALIDATED — A constraint was added NOT VALID and the VALIDATE never followed](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-legacy-constraint-not-validated/): What an unvalidated constraint costs, the one moment it stops being free, and the single statement that closes it. - [DEPLOY.LEGACY.INVALID_INDEX — An index a `CREATE INDEX CONCURRENTLY` never finished](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-legacy-invalid-index/): The leftover helps no query, costs every write, and makes the re-run fail on a name conflict. What the deploy gate reads, and what it deliberately refuses to do about it. - [DEPLOY.PREFLIGHT.DISK_HEADROOM — How much space the pending migration will need, against what the instance can see](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-disk-headroom/): What the check estimates before a deploy, why the honest answer on a managed database is undetermined with the number attached, and what to do with that number. - [DEPLOY.PREFLIGHT.INACTIVE_REPLICATION_SLOT — A replication slot with no consumer is holding WAL](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-inactive-replication-slot/): Which slot has no consumer connected, how much WAL it is holding, what the server's own wal_status says about it, and why the decision to reconnect or drop belongs to a human. - [DEPLOY.PREFLIGHT.LOCK_BLOCKER — Something is already holding a table this migration is about to lock](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-lock-blocker/): What the deploy gate reads out of the live activity views in the minute before a migration, what the finding names about the blocking session, and the decision it deliberately leaves to you. - [DEPLOY.PREFLIGHT.METADATA_LOCK_BLOCKER — a session is sitting on a table this migration is about to change](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-metadata-lock-blocker/): What is holding a metadata lock on the tables the pending migration will alter, and why an empty reading is only a pass when nothing went unread. - [DEPLOY.PREFLIGHT.MISSING_PRIVILEGE — the role running the migrations may not do what they ask for](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-missing-privilege/): What each engine is asked about the migration role, why a privilege MySQL cannot find is undetermined rather than missing, and the grant that answers the finding. - [DEPLOY.PREFLIGHT.REPLICATION_LAG — How far behind the replicas are, one moment before a migration adds to their work](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-replication-lag/): What the check reads on each engine, the two states it reports, the line it judges against, and what to decide when a replica is behind. - [PG.L4.IDENTIFIER_LENGTH — An identifier over PostgreSQL's limit](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l4-identifier-length/): PostgreSQL does not refuse an over-long name, it truncates it and carries on — so the object exists under a name nobody wrote. - [MY.L4.IDENTIFIER_LENGTH — An identifier over MySQL's limit](https://docs.pushery.com/sqlens-for-laravel/rules/my-l4-identifier-length/): MySQL refuses the statement with ERROR 1059 and the deploy stops — the rule moves that failure from the deploy to the diff. - [SEC.CFG.TLS_MIN_VERSION — The server still negotiates a withdrawn TLS version](https://docs.pushery.com/sqlens-for-laravel/rules/sec-cfg-tls-min-version/): Everything reports as encrypted. That is what makes this one harder to notice than TLS being off. - [CAP.L0.RULE_WITHHELD_BY_DEPRECATION — a rule was not applied because it was retired](https://docs.pushery.com/sqlens-for-laravel/rules/cap-l0-rule-withheld-by-deprecation/): A deprecation makes the report shorter and leaves everything in it true, which is exactly what a clean run looks like. - [PG.L9.VIEW_SELECT_STAR — `SELECT *` in a view definition](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l9-view-select-star/): A view over a star freezes its column list at creation, so a column added later never appears — and no migration says so. - [MY.L9.VIEW_SELECT_STAR — `SELECT *` in a view definition](https://docs.pushery.com/sqlens-for-laravel/rules/my-l9-view-select-star/): A view over a star freezes its column list at creation, so a column added later never appears — and no migration says so. - [DEPLOY.CONTEXT.GRANT.OWNERSHIP_MISSING — the migration role holds every grant and still cannot ALTER](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-context-grant-ownership-missing/): PostgreSQL does not let anybody grant ALTER TABLE. It requires ownership, which is why a grant check that stops at privileges can be green and wrong. - [DEPLOY.LEGACY.INVALID_INDEX_NAME_COLLISION — an invalid index is standing where this deploy wants to build one](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-legacy-invalid-index-name-collision/): The same wreckage as a plain invalid index, except the migration about to run will fail on the name. - [PG.L9.TYPE_IMPLICIT_CAST — A foreign key whose two ends are different types](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l9-type-implicit-cast/): An int column referencing a bigint key can address only the first 2.1 billion of it — and the day the sequence passes that number is years away. - [MY.L9.TYPE_IMPLICIT_CAST — A foreign key whose two ends are different types](https://docs.pushery.com/sqlens-for-laravel/rules/my-l9-type-implicit-cast/): MySQL refuses the integer case outright, so what survives and still differs is a pair it accepted and converts on every comparison. - [DEPLOY.PREFLIGHT.CONCURRENT_INDEX_BLOCKER — a concurrent index build is about to wait on unrelated work](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-concurrent-index-blocker/): CREATE INDEX CONCURRENTLY waits for every older transaction, not only the ones touching its own table. The build does not fail — it sits there. - [DEPLOY.PREFLIGHT.STATISTICS_UNREAD — the escalation could not read the object a finding names](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-statistics-unread/): An undetermined that keeps a lint verdict honest: the run could not measure the object, so nothing was raised. - [PG.L9.DOC_MISSING_COMMENT — A table or column with no comment](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l9-doc-missing-comment/): The most opinionated check in the catalog, and off by default — level 9 is not its gate, a config switch is. - [MY.L9.DOC_MISSING_COMMENT — A table or column with no comment](https://docs.pushery.com/sqlens-for-laravel/rules/my-l9-doc-missing-comment/): The most opinionated check in the catalog, and off by default — level 9 is not its gate, a config switch is. - [DEPLOY.DRIFT.UNEXPECTED_IN_DATABASE — The database holds something no migration describes](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-drift-unexpected-in-database/): The hotfix-straight-into-production case: every migration ran, the deploy was green, and the schema still carries an object no file accounts for. What the comparison sees, what it cannot see, and why the next rebuild is the deadline. - [PG.L8.NAMING_SNAKE_CASE — An identifier PostgreSQL will not hand back the way you wrote it](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l8-naming-snake-case/): An unquoted identifier is folded to lower case, so a mixed-case name means one thing quoted and another unquoted. - [MY.L8.NAMING_SNAKE_CASE — An identifier whose meaning depends on the server it lands on](https://docs.pushery.com/sqlens-for-laravel/rules/my-l8-naming-snake-case/): lower_case_table_names differs between a developer machine and a Linux server, so the same migration makes different tables. - [PG.L8.FK_ID_SUFFIX — A foreign key column Laravel cannot derive a relation from](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l8-fk-id-suffix/): The convention is what makes a relation resolve with nothing written down; departing from it is legal and costs an explicit key at every call site. - [MY.L8.FK_ID_SUFFIX — A foreign key column Laravel cannot derive a relation from](https://docs.pushery.com/sqlens-for-laravel/rules/my-l8-fk-id-suffix/): The convention is what makes a relation resolve with nothing written down; departing from it is legal and costs an explicit key at every call site. - [DEPLOY.DRIFT.MISSING_IN_DATABASE — The migrations describe it and the database does not have it](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-drift-missing-in-database/): A migration that failed, was skipped, or was rolled back and never re-applied. Why the loud version of this finding is the harmless one, and how to tell a failed migration from one that never ran. - [DEPLOY.DRIFT.DIVERGENT — Both sides have it, and describe it differently](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-drift-divergent/): The class where the tool is most able to be wrong about itself. What the field-level diff names, why the canonical form is what makes the finding trustworthy, and what to do when the two sides genuinely disagree. - [DEPLOY.LEGACY.ORPHAN_TRANSITION_OBJECT — An object whose name says it was meant to be temporary](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-legacy-orphan-transition-object/): users_old, tmp_backfill_state, orders_20260721 — what an unfinished expand/contract migration leaves behind, reported as a question rather than a verdict. - [DEPLOY.DRIFT.UNCOMPARED — One object type could not be read, so it was never compared](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-drift-uncompared/): The finding that keeps an empty drift report honest. Why "no drift found" and "nothing was looked at" must not share an exit code, which side could not be read, and how to accept it deliberately without muting real drift. - [DEPLOY.LEGACY.OSC_ARTIFACT — What an online-schema-change tool left behind](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-legacy-osc-artifact/): gh-ost's ghost table, pt-osc's triggers, InnoDB's #sql- temporary — names a tool generates, and one of them is still costing you a write on every row. - [DEPLOY.LEGACY.CONSTRAINT_NOT_ENFORCED — A CHECK constraint that is not one](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-legacy-constraint-not-enforced/): ENFORCED = NO is in the catalog, in SHOW CREATE TABLE and in code review — and admits every row it claims to refuse. - [DEPLOY.RUN.TIME_BUDGET_EXCEEDED — The post-deploy run took longer than you allow](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-run-time-budget-exceeded/): The one finding that is about SQLens rather than about your database. Why a run that hangs off every deploy needs a budget it can visibly break, why it is reported rather than aborted, and why sqlens:drift deliberately has none. - [PG.L4.EXPAND_WITHOUT_CONTRACT — A column was added and back-filled, and the one it replaces never dropped](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l4-expand-without-contract/): Expand is the right first step, and only the first. Until the contract step runs, the table carries two columns for one fact and nothing says which is authoritative. - [MY.L4.EXPAND_WITHOUT_CONTRACT — A column was added and back-filled, and the one it replaces never dropped](https://docs.pushery.com/sqlens-for-laravel/rules/my-l4-expand-without-contract/): Expand is the right first step, and only the first. Until the contract step runs, the table carries two columns for one fact and nothing says which is authoritative. - [LINT.ANNOTATION_UNKNOWN_RULE — an annotation suppresses a rule that does not exist](https://docs.pushery.com/sqlens-for-laravel/rules/lint-annotation-unknown-rule/): An `#[SqlensIgnore]` naming a rule id nothing answers to suppresses nothing — while carrying a mandatory reason, so it reads as a decision somebody weighed. - [LINT.SUPPRESSION_DEPRECATED_RULE — a suppression names a rule on its way out](https://docs.pushery.com/sqlens-for-laravel/rules/lint-suppression-deprecated-rule/): The entry still works. It points at a check that will stop checking, and this names the rule that replaced it. - [LINT.SUPPRESSION_TOOL_RULE_UNDESCRIBED — a suppression names a tool rule this build does not describe](https://docs.pushery.com/sqlens-for-laravel/rules/lint-suppression-tool-rule-undescribed/): You may name a rule your installed binary emits without waiting for a release here. It is accepted, and said, because a namespace this package cannot spell-check is where a typo is likeliest. - [PG.L5.MORPHS_NO_INDEX — Polymorphic column pair with no index leading on its type](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l5-morphs-no-index/): A polymorphic relation resolves by both columns at once, so without an index that leads on the type column every read through it scans the table. - [MY.L5.MORPHS_NO_INDEX — Polymorphic column pair with no index leading on its type](https://docs.pushery.com/sqlens-for-laravel/rules/my-l5-morphs-no-index/): A polymorphic relation resolves by both columns at once, so without an index that leads on the type column every read through it scans the table. - [DEPLOY.PREFLIGHT.FREEZE_HORIZON — A table near its freeze horizon, where an anti-wraparound vacuum will not yield](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-freeze-horizon/): Which table this deploy is about to lock is close to running out of transaction ids or multixacts, whether a worker is already on it, and why canceling that worker is the expensive answer. - [PG.L7.AUTOVACUUM_DISABLED — A table somebody switched autovacuum off for](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l7-autovacuum-disabled/): What turning autovacuum off on one table actually costs, why the server takes it back anyway at the freeze horizon, and when the finding is a false positive. - [DEPLOY.PREFLIGHT.AUTOVACUUM_DISABLED — A table this deploy touches has autovacuum switched off](https://docs.pushery.com/sqlens-for-laravel/rules/deploy-preflight-autovacuum-disabled/): Which table in the pending run carries autovacuum_enabled = false, why that makes the report's own size and duration figures for it the least trustworthy ones in it, and why the vacuum arrives anyway. - [PG.L3.LOCK_TIMEOUT_INEFFECTIVE — A lock_timeout that is not smaller than statement_timeout can never fire](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l3-lock-timeout-ineffective/): The statement clock aborts first, so the lock clock is configured, visible and dead — and the failure afterwards is mislabeled as a slow DDL. - [PG.L8.UNIQUE_CASE_FOLDED — A uniqueness guarantee your queries cannot see](https://docs.pushery.com/sqlens-for-laravel/rules/pg-l8-unique-case-folded/): A unique index over lower(column) constrains the folded value, and an ordinary lookup compares something else. ### Optional tools - [Postgres Language Server — the optional schema linter](https://docs.pushery.com/sqlens-for-laravel/tools/pgls/): What its checks add, what SQLens does with them, and what a run does to your database. Optional, never required. - [Optional analyzers — what SQLens will run for you, and what it will not](https://docs.pushery.com/sqlens-for-laravel/tools/optional-analyzers/): The outside tools SQLens can call, what each one adds, and the one it deliberately does not adopt. Every answer here is three-valued: a tool that is absent is reported, never assumed away. ### Security - [Reporting to GitHub code scanning](https://docs.pushery.com/sqlens-for-laravel/security/github-code-scanning/): A workflow that puts SQLens security findings in the Security tab, and the one property that decides how GitHub ranks them. - [The privacy pack](https://docs.pushery.com/sqlens-for-laravel/security/privacy-pack/): An opt-in pack that reads column names and asks whether personal data is stored in the clear — and says out loud what a name cannot tell you. - [The least-privilege audit role](https://docs.pushery.com/sqlens-for-laravel/security/least-privilege/): A copy-paste role for each engine, the exact list of checks that go quiet without more, and the two-connection split that is the single most effective thing you can change. - [Two connections, two roles — the setup that makes an injection cheaper](https://docs.pushery.com/sqlens-for-laravel/security/migration-vs-runtime-connection/): A runtime identity that cannot change the schema turns a successful injection into a smaller incident. Here is the Laravel configuration and the grant script for both engines, with the step everybody forgets. - [MCP transport: the trust boundary, stated](https://docs.pushery.com/sqlens-for-laravel/security/mcp-transport-assumptions/): What you are assuming when you start the SQLens MCP server, and which test holds each assumption. - [MCP threat model: surface, countermeasures, residual risk](https://docs.pushery.com/sqlens-for-laravel/security/mcp-threat-model/): What an agent can reach through the SQLens MCP server, what stops it, and what this design does not protect you from. - [Auditing a database that is not yours](https://docs.pushery.com/sqlens-for-laravel/security/authorization/): Running SQLens against a client's database needs a documented engagement — what that means, what one should contain, and which commands touch a foreign system at all. ## Visual Feedback for Laravel In-page feedback for Laravel apps: a Livewire widget that captures what the reporter actually sees, with a real screenshot and the context you need to reproduce it. - [Visual Feedback for Laravel](https://docs.pushery.com/visual-feedback-for-laravel/): In-page feedback for Laravel: a widget on the page the reporter is already looking at, and a report that arrives with a screenshot of what they actually saw. - [Installation](https://docs.pushery.com/visual-feedback-for-laravel/installation/): Requirements, the publish tags, the layout the widget goes in, and the settings a working install still needs afterwards. - [The capture cascade](https://docs.pushery.com/visual-feedback-for-laravel/capture/): The two capture stages — the browser's own screen capture and the DOM renderer — and how to tell which one produced a report. - [Placing the trigger](https://docs.pushery.com/visual-feedback-for-laravel/placing-the-trigger/): Use the built-in floating button, place your own trigger anywhere, or open the widget from your own code. - [View trees](https://docs.pushery.com/visual-feedback-for-laravel/view-trees/): The framework-free views and the WireKit variant: what each one assumes about your application, and how to switch. - [Configuration](https://docs.pushery.com/visual-feedback-for-laravel/configuration/): Every configuration key and its environment variable, with the defaults each one falls back to. - [Delivery channels](https://docs.pushery.com/visual-feedback-for-laravel/delivery-channels/): Where a report goes — mail, database, webhook — and how to add a channel of your own. - [Abuse protection](https://docs.pushery.com/visual-feedback-for-laravel/abuse-protection/): The built-in floor that no gate choice can switch off, its one deliberate off switch, and how to layer an additional gate on top of it. - [Privacy and retention](https://docs.pushery.com/visual-feedback-for-laravel/privacy-and-retention/): The guest privacy notice, the optional legal-consent bridge, and the three commands that handle the data lifecycle. - [Accessibility](https://docs.pushery.com/visual-feedback-for-laravel/accessibility/): What is proven rather than claimed — and the three things the host page owes that the widget cannot supply. - [Integration contract](https://docs.pushery.com/visual-feedback-for-laravel/integration-contract/): What the capture reproduces, what it does not, and the one limitation that matters for hiding sensitive content. - [Testing](https://docs.pushery.com/visual-feedback-for-laravel/testing/): How to exercise the package in your own application, and what the bundled suites already prove. - [Report browser](https://docs.pushery.com/visual-feedback-for-laravel/report-browser/): An optional Livewire view over the reports table: filter, read, delete. It is unreachable until you route it and open a gate, and both are deliberate. ## Webhooks for Laravel An all-in-one, config-gated webhooks toolkit for Laravel: send signed outbound webhooks, receive and verify inbound ones, let customers manage their own endpoints, and observe every delivery. - [Webhooks for Laravel](https://docs.pushery.com/webhooks-for-laravel/): An all-in-one, config-gated webhooks toolkit for Laravel — send, receive, let customers self-serve, and observe every delivery, with only the layers you switch on. - [Installation](https://docs.pushery.com/webhooks-for-laravel/installation/): Requirements, composer require, the per-layer migration tags, and the two packages every shipped screen needs before it can render. - [Quickstart](https://docs.pushery.com/webhooks-for-laravel/quickstart/): Send a signed webhook, receive and verify one, or run the sender alone with no database — the three shortest paths through the package. - [Choosing your database](https://docs.pushery.com/webhooks-for-laravel/choosing-your-database/): The three storage topologies, what PostgreSQL buys over MySQL (cost at scale, never correctness), the collation trap that silently drops webhooks, and the dedicated-connection side-car. - [Signatures and interop](https://docs.pushery.com/webhooks-for-laravel/signatures-and-interop/): The Standard Webhooks wire format the package signs and verifies, every shipped scheme, custom header names, canonical JSON, and the published known-answer vectors a third-party port can self-verify against. - [Events](https://docs.pushery.com/webhooks-for-laravel/events/): The two event families — the transport events every delivery fires, and the Platform domain events that only exist while the Platform layer runs — and which one a send-only app gets. - [Security](https://docs.pushery.com/webhooks-for-laravel/security/): How outbound URLs are vetted and pinned, what is encrypted at rest, which headers are redacted, and the one config key whose name means the opposite of what it does. - [Reliability](https://docs.pushery.com/webhooks-for-laravel/reliability/): Retries and backoff, idempotency, the circuit breaker and its manual recovery path, traffic-shaping rate limits, and how retention differs between PostgreSQL and MySQL. ### The layers - [Sending (Server layer)](https://docs.pushery.com/webhooks-for-laravel/layers/sending/): The immutable PendingWebhook builder — secret rotation, alternate signing schemes, backoff and Retry-After, timeouts, SSRF, mutual TLS, proxies, tags, and standalone delivery persistence. - [Receiving (Client layer)](https://docs.pushery.com/webhooks-for-laravel/layers/receiving/): The inbound pipeline in order — raw-body capture, verification, throttling, dedupe, filtering, storage and dispatch — plus every shipped receive adapter and the non-signature verifier seam. - [Subscriptions and fan-out (Platform layer)](https://docs.pushery.com/webhooks-for-laravel/layers/subscriptions-and-fan-out/): Register endpoints per event type, fan an event out to every matching subscription, and switch on health scoring, payload transforms, the egress allowlist and AsyncAPI export. - [Self-service portal (opt-in)](https://docs.pushery.com/webhooks-for-laravel/layers/self-service-portal/): The tenant-scoped screens a customer uses to manage its own endpoints — the three required setup steps, the fail-closed gate, and how to resolve a non-Jetstream tenant. - [Observability dashboard (opt-in)](https://docs.pushery.com/webhooks-for-laravel/layers/dashboard/): The customer-facing analytics UI over the delivery log — the three setup steps, the fail-closed gate, tenant and operator mode, the percentile tiers, and the optional JSON metrics endpoint. - [Operator console (opt-in)](https://docs.pushery.com/webhooks-for-laravel/layers/operator-console/): Two embeddable Livewire components for the screens you run — deliberately unscoped across every tenant, so they belong behind an operator-only gate. ### Guides - [Styling the UI](https://docs.pushery.com/webhooks-for-laravel/guides/styling-the-ui/): The package ships no compiled stylesheet — the two Tailwind source registrations every screen needs, the icon set, dark mode, and embedding under a strict Content-Security-Policy. - [Localization](https://docs.pushery.com/webhooks-for-laravel/guides/localization/): Seven shipped languages under the webhooks namespace, one file per surface plus the number separators, how to override a single string, and how to add a locale. - [Querying the tables yourself](https://docs.pushery.com/webhooks-for-laravel/guides/querying-the-tables/): Why a plain where('created_at', …) against the delivery log is silently wrong on both engines and the shipped scopes that bind the instant correctly per dialect, plus the three columns that refuse a hand-written row — one of them on only one engine. - [Scheduled maintenance](https://docs.pushery.com/webhooks-for-laravel/guides/scheduled-maintenance/): What the package schedules against the default connection, how to notice when one of those jobs stops, and why a database-per-tenant host must switch it off and run the commands inside its own tenant loop. - [Testing your integration](https://docs.pushery.com/webhooks-for-laravel/guides/testing-your-integration/): Assert the fan-out with Bus::fake(), drive a real request end to end, and what the package's own suite needs if you are working on the package itself. - [Upgrading from 0.x](https://docs.pushery.com/webhooks-for-laravel/guides/upgrading-from-0x/): Version 1.0.0 is a ground-up rewrite — the new sender, the new default signature, the receive adapter that keeps your existing consumers verifying, and the reorganized config. - [Coming from another webhook package](https://docs.pushery.com/webhooks-for-laravel/guides/migrating-from-another-package/): A superset of a send-only and a receive-only webhook package on either engine — what replaces what, and the idempotent backfill that carries an existing webhook_calls history across whatever its columns are called. - [Upgrading from 1.x](https://docs.pushery.com/webhooks-for-laravel/guides/upgrading-from-1x/): 2.0.0 moves the package namespace to Pushery\Webhooks. What composer handles, the six things it does not, and why no database migration is needed. - [Upgrading from 2.x](https://docs.pushery.com/webhooks-for-laravel/guides/upgrading-from-2x/): 3.0.0 renames the backlog import and drops a column nothing ever wrote. Three changes, one of which can duplicate an imported history if you skip it. - [Searching the logs](https://docs.pushery.com/webhooks-for-laravel/guides/searching-the-logs/): The full-text index over the delivery and call logs is opt-in and takes three steps, none of which happens by flipping search.enabled — Scout is a suggestion, not a dependency, a ready-made searchable model has to be pointed at, and the payload is behind a fourth switch of its own. ### Reference - [Configuration reference](https://docs.pushery.com/webhooks-for-laravel/reference/configuration/): The config section tree, every layer gate and its default, the sub-feature switches that start off, and the per-call overrides the sending builder applies on top. - [Command reference](https://docs.pushery.com/webhooks-for-laravel/reference/commands/): Every Artisan command the package registers, what it does, its options, and which layer schedules it — plus the two model:prune entries that are easy to miss. - [Publishable tags](https://docs.pushery.com/webhooks-for-laravel/reference/publishable-tags/): Every vendor:publish tag the package registers, what it writes, and why you publish one migration tag per layer rather than all of them. - [Versioning and the public API](https://docs.pushery.com/webhooks-for-laravel/reference/public-api/): The package follows Semantic Versioning and names exactly which classes that promise covers — everything else is @internal and may change in a minor release. - [Status values and exceptions](https://docs.pushery.com/webhooks-for-laravel/reference/enums-and-exceptions/): The stored status enums and their exact string values, and every exception the package throws — what each one means and whether catching it is the right move.