# Artisan-Migrate — Implementation Tasks

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

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

## Prerequisites

- [ ] The admin auth stack is available (the `['web','admin']` middleware and `auth/login` gate — see the `auth` unit).
- [ ] A configured database connection reachable by the app, and the `migrations` ledger table (created by the framework on first migrate).
- [ ] The `database/migrations/` set present in the reimplementation.
- [ ] **Human decision required before shipping:** confirm whether an HTTP-triggered migration endpoint should exist in production at all, and if so under what protection (see Pending Gaps).

## Tasks

> Each task references the legacy file the behaviour was extracted from.

- [ ] T-01, Register `GET /artisan` inside the admin route group (empty prefix, `['web','admin']` middleware) so it is reachable only by an authenticated admin.
  - Legacy origin: `routes/web.php:23-31`, `config/admin.php:24-30`
  - Done when: an anonymous `GET /artisan` returns `302 auth/login`; an authenticated admin reaches the handler.
  - Confidence: 🟢

- [ ] T-02, Invoke the migrator non-interactively: `Artisan::call('migrate', ['--force' => true])`.
  - Legacy origin: `routes/web.php:32-34`
  - Done when: hitting the endpoint applies all pending migrations (new rows in the `migrations` table) without prompting for confirmation.
  - Confidence: 🟢

- [ ] T-03, Return the completion response body `Migrating completed<br>` (HTTP `200`, `text/html`) after the call returns.
  - Legacy origin: `routes/web.php:36`
  - Done when: a successful call responds with the literal string and `200`.
  - Confidence: 🟢

- [ ] T-04, Preserve the observed no-outcome behaviour of the legacy (exit code discarded, `dd` commented out) OR — recommended — capture `$exitCode` and report it. Document which was chosen.
  - Legacy origin: `routes/web.php:32,35`
  - Done when: behaviour matches the legacy (static message) unless a deliberate improvement is signed off (see T-06).
  - Confidence: 🟢 (legacy behaviour) / 🔴 (whether to improve)

## Improvement Tasks (require sign-off — not legacy behaviour)

- [ ] T-05, Move the trigger off `GET` (use `POST` with CSRF, or a signed/one-time URL, or restrict to CLI/deploy only) to remove the prefetch/crawler side-effect risk.
  - Legacy origin: `routes/web.php:31` (the gap being remediated)
  - Done when: the operation can no longer be triggered by a safe/idempotent request; requires the human decision below.
  - Confidence: 🔴 (design decision)

- [ ] T-06, Add observability + a real outcome: log who/when/result, surface the exit code (or catch exceptions) and report success/failure in the response instead of a static string.
  - Legacy origin: `routes/web.php:32,35,36` (absence)
  - Done when: each invocation is auditable and the response distinguishes success from failure.
  - Confidence: 🔴 (design decision)

- [ ] T-07, Add authorization beyond authentication (a dedicated permission/role for running migrations), addressing ADR-0009.
  - Legacy origin: `config/admin.php:29` (absence of permission middleware)
  - Done when: only an explicitly-permitted admin can trigger the endpoint.
  - Confidence: 🔴 (design decision)

## Test Tasks

- [ ] TT-01, Happy path: an authenticated admin `GET /artisan` applies pending migrations and returns `Migrating completed<br>` (see `requirements.md`, Acceptance Criteria).
- [ ] TT-02, Auth guard: an anonymous `GET /artisan` returns `302 auth/login` and runs no migration.
- [ ] TT-03, Idempotence: a second `GET /artisan` with no pending migrations applies nothing and still returns the completion message.
- [ ] TT-04, Failure path: a migration that throws propagates as `500` (documents that the static message does not confirm the true outcome).

## Data Migration Tasks (if applicable)

- [ ] TM-01, This unit **is** the migration trigger; ensure the migration set (`database/migrations/`) is ported and its run order is preserved. Confirm the `migrations` ledger table exists on the target.
  - Legacy origin: `database/migrations/`, `routes/web.php:32`

## Suggested Order

1. T-01 → T-02 → T-03 to reproduce the legacy behaviour faithfully, then T-04.
2. Resolve the Pending Gaps (below) with the operations team before T-05/T-06/T-07 — those change the security posture and must not be applied silently.

## Decision (2026-09-21)

Reviewed with the project owner: **keep the legacy behaviour as-is.** `GET /artisan` stays a `--force`-migrated, unauthenticated-result, admin-authenticated-only endpoint — no verb change, no CSRF/signed-URL hardening, no extra authorization, no outcome reporting added. The improvement tasks below (T-05/T-06/T-07) are documented options, not scheduled work; they require a separate explicit sign-off to pick up later.

## Accepted Risk (was Pending Gaps 🔴)

- Side-effecting `GET` behind authentication only, with the real exit code discarded — accepted as a known, unaddressed risk rather than an open question. (`routes/web.php:31-36`)
