# Artisan-Migrate — Technical Design

> Produced by the Reversa **Writer** (phase: generation) · doc_level: `complete`
> Generated on 2026-09-21

**Confidence scale:** 🟢 CONFIRMED · 🟡 INFERRED · 🔴 GAP

## Interface

HTTP endpoint (inline closure, no controller, no route name):

| Method | Path | Input | Output | Status codes |
|--------|------|-------|--------|--------------|
| GET | `/artisan` | none (no query/body/path params) | `text/html` body: `Migrating completed<br>` | 200, 302 (→ `auth/login` when unauthenticated), 500 (a migration throws) |

The path resolves to `/artisan` because the admin group prefix is empty (`config('admin.route.prefix') === ''`). 🟢 (`routes/web.php:24`, `config/admin.php:25`)

Underlying framework call:

| Symbol | Signature | Return | Note |
|--------|-----------|--------|------|
| `Artisan::call` | `call('migrate', ['--force' => true])` | `int $exitCode` | Runs the `migrate` console command programmatically; `$exitCode` is captured then discarded. 🟢 (`routes/web.php:32-34`) |

## Main Flow

1. A request hits `GET /artisan`. The `web` + `admin` middleware run first; an unauthenticated request is redirected to `auth/login` (`302`) and the closure never executes. 🟢 (`routes/web.php:23-31`, `config/admin.php:29`)
2. The closure calls `\Artisan::call('migrate', ['--force' => true])`. The migrator inspects the `migrations` table and runs every migration file in `database/migrations/` that has not yet been recorded, in order, marking each as run. `--force` suppresses the interactive production-confirmation prompt. 🟢 (`routes/web.php:32-34`)
3. The integer exit code is assigned to `$exitCode`. The only line that would consume it, `//dd($exitCode);`, is commented out, so the value is discarded. 🟢 (`routes/web.php:32,35`)
4. The closure `echo`s the literal string `"Migrating completed<br>"`; Laravel wraps the echoed output into an HTTP `200` response with `Content-Type: text/html`. 🟢 (`routes/web.php:36`)

## Alternative Flows

- **Unauthenticated request:** the admin middleware redirects to `auth/login` (`302`); no migration runs. 🟢 (`routes/web.php:20,23-29`)
- **No pending migrations:** `Artisan::call('migrate')` applies nothing (already-recorded migrations are skipped) and returns `0`; the response is still `"Migrating completed<br>"`. 🟢 (Laravel migrator semantics)
- **A migration throws (SQL error, bad migration):** the exception is **not** caught in the closure, so it propagates to the framework handler as an HTTP `500`. Because migrations here are not wrapped in a single transaction by this route, the schema may be left partially migrated (each migration's own transactional behaviour depends on the DB engine / migration content). 🟡 (`routes/web.php:32-36` — absence of try/catch)
- **Non-zero exit without exception:** the migrator returns a non-zero code but does not throw; the response still reports `"Migrating completed<br>"`, masking the failure. 🟡 (`routes/web.php:32,35,36`)

## Dependencies

- **Laravel Artisan / Migrator** — runs the `migrate` command programmatically and reads/writes the `migrations` ledger table. 🟢 (`routes/web.php:32`)
- **`database/migrations/`** — the set of migration classes actually executed. 🟡 (applied by the call)
- **Admin auth middleware (`['web','admin']`)** — the only access gate; authenticates the session before the closure runs. 🟢 (`config/admin.php:29`)
- **Database connection** — the migrator applies DDL against the configured connection; a misconfigured connection fails the call. 🟡 (`config/database.php`)

## Identified Design Decisions

| Decision | Evidence in code | Confidence |
|----------|------------------|------------|
| Migrations are triggerable over HTTP as a deployment convenience (no shell access assumed) | `routes/web.php:31-37` | 🟡 |
| `--force` is passed so the call runs non-interactively in production | `routes/web.php:33` | 🟢 |
| Implemented as an inline closure rather than a controller action | `routes/web.php:31-37` | 🟢 |
| Success message is static text, decoupled from the actual exit code (`dd` commented out) | `routes/web.php:32,35,36` | 🟢 |
| Placed behind the admin auth group only — no dedicated permission/role | `routes/web.php:23-31`, `config/admin.php:29` | 🟡 |

## Internal State

The unit holds no state of its own. Its persistent effect is entirely in the database: the `migrations` ledger table gains a row per newly-applied migration, and the schema changes those migrations describe are applied. 🟢 (Laravel migrator; `routes/web.php:32`)

## Observability

None. The exit code is captured but discarded (`//dd($exitCode);` is commented out), and there is no log, metric, audit entry, or trace recording **who** triggered the migration, **when**, or the **result**. On success or masked failure the caller sees only the static `"Migrating completed<br>"`; on a thrown migration the only signal is the framework's default `500` page. 🔴 (`routes/web.php:32,35`)

## Risks and Gaps

- 🟢 **Side-effecting `GET` behind authentication only — accepted, 2026-09-21.** A schema-mutating operation on a verb clients treat as safe (prefetch/crawler/mistyped URL risk), no CSRF barrier, no confirmation step. Reviewed with the project owner; kept as legacy behaviour, no change scheduled. (`routes/web.php:31`)
- 🟢 **Outcome is not reported — accepted, 2026-09-21.** `$exitCode` is discarded and there is no try/catch, so a non-zero exit is indistinguishable from success in the response, and there is no audit trail. Reviewed and kept as-is. (`routes/web.php:32,35`)
- 🟡 **No transaction / partial-migration risk.** The route does not wrap the run; a failure mid-set can leave the schema half-migrated with no automatic rollback from this endpoint. 🟡 (`routes/web.php:32-36`)
- 🟡 **Authentication only, no authorization.** Any authenticated admin can run migrations (ADR-0009). 🟡 (`config/admin.php:29`)
- 🟡 **Closure route consequences.** A closure route cannot be serialized by `route:cache`, and the migration-trigger logic is not isolated for unit testing. 🟡 (`routes/web.php:31-37`)
- 🟡 **`--force` in production is intentional but removes the safety prompt** — acceptable for a web trigger, but it means an accidental hit runs immediately with no guard. 🟡 (`routes/web.php:33`)
