NAME
Punk::Plugin::Sqitch - the schema check at boot
SYNOPSIS
package MyApp;
use Punk;
database dsn => 'dbi:Pg:dbname=shop', user => 'shop',
password => $ENV{SHOP_DB_PASSWORD};
plugin 'Sqitch'; # pending changes: croak at boot
plugin 'Sqitch' => { check => 'warn' }; # say so, start anyway
plugin 'Sqitch' => { check => 0 }; # off
DESCRIPTION
Is the schema behind the code? This plugin asks once, at boot, in the parent before the workers fork, with the same computation punk sqitch pending runs: the application's sqitch.plan against the Sqitch registry in its database. An application started against a schema that is missing its latest changes fails on its first query anyway, with a message about a column; this fails at boot, naming the changes and the command that applies them.
Three outcomes, because they are three conditions:
Changes pending - the plan lists changes the registry does not hold. A croak naming them (
check => 'croak', the default) or a warning ('warn'), because the deploy shipped without its migration and "fail atto_app, not at 3am" is the rule.The database is away - the registry cannot be reached. A warning, and the application starts: the database being unreachable at boot is not the schema being behind, and Punk::Model connects lazily for the same reason.
Drift - the registry holds more changes than the plan, or its last deployed change is not the plan's entry at that position. Something was deployed here that the plan does not describe, and no
deployfixes it. A croak or a warning, as for pending.
A missing sqitch.plan croaks at the plugin line: the plugin was asked for and there is nothing to check. check => 0 turns the check off for the deployment that migrates after the new code is up, on purpose.
When it runs
On a Punk with $app->on_compile (0.31 and later) the check runs at to_app, after every keyword has recorded, so it sees the database the database keyword declared wherever the plugin line sits. Before that it runs at the plugin line and reads config/punk.yml for the environment - PUNK_ENV, else production, or the env option - which is where a deployed application's database is configured.
The check covers every project: the application's own and each one a plugin registered (see "FOR PLUGIN AUTHORS"), and its message names each project's pending changes, plugins first.
FOR PLUGIN AUTHORS
A plugin that needs tables ships them as a Sqitch project - a directory holding sqitch.plan with %project=NAME and the deploy/, revert/ and verify/ scripts - and registers it:
# in your plugin's register
Punk::Plugin::Sqitch->project($app, punk_apikey => $dir,
engines => [qw(sqlite pg)])
if Punk::Plugin::Sqitch->can('project');
The can guard is the contract: your plugin must work without Punk-Sqitch installed, documenting its DDL in its POD for the application to apply however it applies schema. With it installed, punk sqitch deploy deploys your project before the application's own, into the same database and registry under your project's name; revert unwinds in the opposite order; status, verify, log, check and pending cover it; and the boot check names it.
$dir is wherever your distribution keeps the project - a share directory through File::ShareDir, or a path computed from your own %INC entry; the method takes a directory and does not care which.
Dependencies between projects
Your plan may require another plugin's change by project name - sqitch add keys --requires punk_auth:users - and Punk-Sqitch orders the projects so that a project deploys after every project it requires, registration order breaking ties. A cycle croaks naming it. A requirement on a project no plugin registered croaks naming your plugin; a requirement on the application's own project croaks, because the application deploys last. The application's plan may require your changes the same way.
engines
engines => [qw(sqlite pg)] says which engines your scripts are written for; an application on another engine croaks at boot naming the project and the list, rather than at deploy with a syntax error. Per engine scripts are Sqitch's own arrangement: a project directory per engine, or the engine-keyed script directories its documentation shows.
Two rules that are forever
The project name is the registry's key. Renaming it orphans every deployed change under the old name in every database your plugin was ever deployed to. punk_<plugin>, lowercase, and never changed.
A deployed script is never edited. Once a change of yours is in somebody's production registry, your next release may add changes, or rework one - Sqitch's way of changing a deployed change - and may not edit the script in place: Sqitch's check reports the divergence and a fresh deploy runs different SQL from the one the registry records. This is Sqitch's rule, stated here because a library author will not have met it.
The registry keeps its default name
A plugin's project runs from its own directory with no sight of the application's sqitch.conf, so a registry renamed there would leave the plugins writing to one registry and the application to another. With plugin projects registered the registry keeps Sqitch's default name, and a renamed one croaks saying so.
OPTIONS
check-
'croak'(default),'warn', or0. database-
The configured database to check; the default one when omitted.
dir-
Where to look for the application (app.psgi, walking up); the current directory by default. The plan and sqitch.conf are read relative to the root found.
env-
The environment config/punk.yml is resolved for, when the check reads the file.
WHAT IT READS
The application's project under sqitch/ ("The sqitch directory" in Punk::Sqitch): sqitch.conf for plan_file, top_dir and registry with Sqitch's own precedence (target.*, engine.*, core); only the project's own file, not ~/.sqitch/sqitch.conf. The registry itself: for SQLite the file Sqitch keeps beside the target (the registry name in place of the database's basename - app.db and sqitch give sqitch.db), for PostgreSQL the sqitch schema, for MySQL the sqitch database. One SELECT, through DBI, with no App::Sqitch loaded: this runs in a web worker's parent, where loading a Moo application to answer a yes/no question would be the wrong cost.
METHODS
register($app, \%opts)
The plugin entry point; see "OPTIONS".
project($app, $name => $dir, %opts)
Register a plugin's Sqitch project for the application $app is the registrar of; see "FOR PLUGIN AUTHORS". Croaks at the plugin line on a name Sqitch would refuse, a directory without a plan, a plan whose %project is not $name, a second registration of the name (naming both owners), or an unknown option.
projects_for($app_or_class, app_project => $name?)
The registered projects in deploy order; croaks on a cycle or an unregistered requirement.
result_for($app_class)
The last check's result hash for the application's own project (see "pending" in Punk::Sqitch), for tests.
SEE ALSO
Punk::Sqitch, Punk::Command::Sqitch, Punk::Plugin.
AUTHOR
LNATION <email@lnation.org>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)