TL;DR
- Fast-forwarded two Laravel apps off an old in-house skeleton — in layers, not one heroic merge.
- Added a security-header middleware and a retention purge command whose guard rails are the real feature.
- Built a reconciler for queued jobs that never ran (own post).
- Shipped a 404 page and a QR-code redirect on the company website.
Fast-forwarding an app off an old skeleton
Both product apps were generated from an in-house skeleton (auth, roles, menus, audit). One had drifted ~30 minor versions behind. A skeleton is a starting point, not a dependency — once you generate from it, upstream fixes don't flow to you.
What made it tractable: layers, as separate commits.
| Layer | What goes in | Risk |
|---|---|---|
| 1. Packages | New composer packages, nothing wired | Low |
| 2. Assets + docs | Icons, vendor assets, feature docs | Low |
| 3. Config + env | Config files, .env.example, access control |
Medium |
| 4. Infrastructure | Queue/Horizon config, static pages | Medium |
| 5. Wiring | Providers, middleware, menus | High |
| 6. Cheap wins | Trusted proxies, UUID column on audits | Low |
Each layer reverts independently. Compare that to one 68-file commit where the only rollback is "undo everything."
Underrated cheap win in there: a UUID column on the audit table. Auto-increment internally, UUID for anything leaving the system. Retrofitting that after other systems reference audit rows by integer id is far more painful.
Security headers, default-on
Nothing exotic here — the value is that it's a default, not a per-route decision:
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
if (app()->environment('production')) {
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
// ...CSP, when enabled
One nuance worth stealing: CSP on in production, off locally. Vite's dev server and HMR trip over a strict policy, and an always-on CSP just teaches the team to disable it. Sensible default, env flag to override.
Retention purge: the guard rails are the feature
The data:purge command is boring — delete where created_at < cutoff. What matters is what it refuses to do.
| Guard | Why |
|---|---|
| Retention from config, not hard-coded | A hard-coded 365 shreds years of history every night |
| Refuses the append-only audit trail | CRUD diffs are prunable; the governance log is not |
--dry-run |
Never point a delete loop at production blind |
Two audit concepts, two lifetimes. A framework CRUD-diff table ("field X went A → B") is operational data — prune it. An append-only domain audit log is the record. Encoding that as an early return is cheaper than remembering it at 2am.
Reconciler for jobs that never ran
Rows stuck pending forever, nothing in failed_jobs — the job was dispatched once at creation and that dispatch got lost. Retries can't save a job that never entered the queue; a scheduled sweeper over stale, unclaimed rows can. That got its own post ("The Reconciler Pattern").
Bonus lesson: I registered the sweeper's cron from a deploy step, then reverted it an hour later. Deploy-time schedule seeding is convenient until a rollback re-registers a schedule you deliberately removed.
Website: a 404 and a redirect
On the public company website: a real 404 page, plus /company-profile — the URL printed on a QR code — redirecting to the profile document.
const url = import.meta.env.VITE_COMPANY_PROFILE_URL;
// External URL → full page navigation; <Navigate> only does in-app routes.
// Env unset → fall back home rather than dead-end a printed URL.
if (!url) return <Navigate to="/" replace />;
The fallback is the point. Any URL living outside your codebase — QR code, email footer, business card — is a permanent public API. Treat it like one.
Takeaway
Accidental theme: defaults and fallbacks. Layered upgrades so rollback is cheap. Headers on by default. A purge that refuses the dangerous table. A redirect that never dead-ends. Not clever — just the stuff that prevents a 2am page.