Skip to content
prod 352bb92
Browse

1 · Technical readiness (MUST)

Objective — transfer Phase 10 audit deferrals into the canonical Zaj-BACKLOG.md, then run the seven MUST gates that verify the selected launch-candidate system from Zaj-PROJECT.md against reality (not against your local machine) so a real customer never hits a problem that a pre-launch check would have caught.

Steps at a glance:

  1. Verify git & deployment — Confirm the launch-candidate environment runs the exact committed code: clean git status, deployed commit hash matches the intended release, the current symlink points at the newest release, critical symlinks (storage, bootstrap/cache, public/build, public/storage, and public/packages for in-tree module apps) resolve instead of merely existing.
  2. Scan codebase health & security — Validate config/route/view caching, boot the app, then run static analysis and the zero-tolerance security sweep: hardcoded secrets, SQL injection (DB::raw / whereRaw / selectRaw must be parameterized), mass-assignment guards on every model, CSRF on every POST form, audited raw-output ({!! !!}) sites, and deployed vendor/ backdoor signatures.
  3. Confirm database confidence — Migrations are the source of truth — identical migration files, all applied, mean identical schemas.
  4. Audit environment security — On the launch-candidate environment, confirm the expected APP_ENV, APP_DEBUG=false, an APP_KEY starting with base64:, the correct APP_URL, and that .env was never committed.
  5. Run end-to-end testing — Walk the real user journeys against the launch-candidate environment with payment test keys: signup (target < 90s), email verification, login, core CRUD, payment success and decline, mobile on a physical device, profile/password changes, file upload, and password reset.
  6. Wire monitoring & observability — Confirm error tracking receives a deliberately thrown test exception within a minute, alert rules exist (new issue, high volume, critical), uptime monitors watch the homepage, login, and a /health endpoint, log rotation keeps 14+ days, and recent backups exist.
  7. Complete final signoff — Run the tiered go/no-go on the next page. Tier 1 must be 13/13 or the launch does not happen.

These gates run in order — each one assumes the previous passed. Start by carrying forward any deferred audit work so it does not disappear. Then test the launch-candidate system as it actually runs on the server, because a clean local machine proves nothing about the deployed release. Phase 12 repeats the production-only checks after production is deployed.

First — transfer the audit deferrals into Zaj-BACKLOG.md

Section titled “First — transfer the audit deferrals into Zaj-BACKLOG.md”

Before the MUST checks, copy each 📋 Deferred to Zaj-BACKLOG.md row from the Phase 10 consolidated verdict into the project’s canonical Zaj-BACKLOG.md at the repo root. Zaj-BACKLOG.md is the single deferred-work tracker; do not create a separate per-project polish file. Promote a row to a GitHub Issue only when it is scheduled.

Each transferred row must carry:

  • Source — which audit and dimension surfaced it.
  • Severity — copied from the consolidated verdict.
  • Effort — file count, hours, or clear size estimate.
  • Trigger — when the work should be scheduled.
  • Acceptance criterion — specific, testable outcome.
  • Labels — function / type / priority labels that match the root Zaj-BACKLOG.md format.
  1. Locate the latest consolidated report and transfer the deferred rows.

    Terminal window
    LATEST_REPORT=$(ls -t Admin-Local/1-Project/4-Audit-Reports/*phase-10-CONSOLIDATED.md 2>/dev/null | head -1)
    test -n "$LATEST_REPORT" && echo "OK consolidated report: $LATEST_REPORT" || echo "🔴 missing consolidated report — STOP"
    grep -nE "GO|CONDITIONAL GO|NO-GO" "$LATEST_REPORT" | head -5
    test -f Zaj-BACKLOG.md && echo "OK Zaj-BACKLOG.md exists" || echo "🔴 Zaj-BACKLOG.md missing — return to Phase 1 Step 5 and seed the root trackers first"
    # Expected: report exists, verdict is GO or accepted CONDITIONAL GO, Zaj-BACKLOG.md exists
    • ✅ Every 📋 Deferred to Zaj-BACKLOG.md row is copied into root Zaj-BACKLOG.md with source, severity, effort, trigger, acceptance criterion, and function / type / priority labels.

Confirm the launch-candidate environment runs the exact committed code: clean git status, deployed commit hash matches the intended release, the current symlink points at the newest release, critical symlinks (storage, bootstrap/cache, public/build, public/storage, and public/packages for in-tree module apps) resolve (not just exist — a shipped absolute symlink to a dead vendor path 403/404s every /storage/* asset), the installer-complete marker is present and lives in shared storage, no dev-only files leaked into the release, and the scheduler cron entry exists. Use the selected launch-candidate environment row from Zaj-PROJECT.md (staging-primary, uat, client-demo, or production if production already exists).

  1. Confirm the deployed tree matches the release.

    Terminal window
    git status --porcelain # Expected: nothing
    git fetch --all
    readlink current # Expected: points at newest releases/* dir
    php artisan schedule:list # Expected: scheduler entry present
    • ✅ Working tree is clean, current points at the newest release, and the scheduler entry is present.
  2. Confirm the per-environment symlinks resolve on the launch-candidate environment — not just that they exist. A CodeCanyon archive frequently ships public/storage tracked as an absolute symlink to the vendor’s own server path (/var/www/html/.../storage/app/public), which is dead on every other host — so all /storage/* assets 403/404 even though the link “is present.” For in-tree module apps (e.g. WorkDo) the same applies to public/packages. Verify each link points inside this release and resolves.

    Terminal window
    SSH_LAUNCH_ALIAS="<launch-candidate-alias-from-Zaj-PROJECT>"
    ssh "$SSH_LAUNCH_ALIAS" 'cd ~/domains/<DOMAIN>/deploy/current && for l in public/storage public/packages; do
    [ -L "$l" ] || { echo " ⬜ $l: not a symlink (skip if app does not use it)"; continue; }
    tgt=$(readlink "$l")
    if [ -e "$l" ] && [ -d "$l" ]; then echo " ✅ $l → $tgt (resolves)"; \
    else echo " 🔴 $l → $tgt (DEAD — relink: rm $l && php artisan storage:link, or cd public && ln -s ../packages packages)"; fi
    done'
    # Expected: every symlink the app uses resolves to a directory inside the shared storage / in-tree packages dir
    • public/storage (and public/packages if the app ships in-tree module assets) resolve to a real directory on the launch-candidate environment — not a dead absolute path. If dead, relink and re-fetch one /storage/... asset over HTTPS to confirm 200, not 403/404.
  3. Confirm the installer-complete marker is present on the launch-candidate environment and lives in shared storage. The vendor’s marker (default storage/installed — use the path recorded in Phase 2 §5) is per-environment runtime state, like .env: it must exist on an installed environment (or the app re-enters the installer on the next request) and it must live in the shared storage/ dir so it survives every deploy. It must never have entered Git history or been baked into the deploy payload.

    Terminal window
    MARKER="storage/installed" # ← swap for your vendor's recorded marker path
    SSH_LAUNCH_ALIAS="<launch-candidate-alias-from-Zaj-PROJECT>"
    ssh "$SSH_LAUNCH_ALIAS" "cd ~/domains/<DOMAIN>/deploy/current && \
    { [ -e \"$MARKER\" ] && echo ' ✅ marker present (app is installed)'; } || echo ' 🔴 marker MISSING — launch-candidate app is not installed; run the vendor installer before signoff'; \
    readlink -f \"$MARKER\" 2>/dev/null | grep -q '/shared/' && echo ' ✅ marker resolves into shared storage (persists across deploys)' || echo ' ⚠️ marker is not under shared storage — it will be lost on the next deploy; move storage/ to a shared mount'"
    ssh "$SSH_LAUNCH_ALIAS" "cd ~/domains/<DOMAIN>/deploy/current && \
    git ls-files --error-unmatch \"$MARKER\" 2>/dev/null" \
    && echo " 🔴 marker is TRACKED in git — a fresh server would skip its installer; git rm --cached it" \
    || echo " ✅ marker is not tracked (correct — gitignored per-env state)"
    • ✅ The marker exists on the launch-candidate environment, resolves into the shared storage/, and is not tracked in git. A missing marker means the app is not installed; a committed/payload-baked marker is the first-install hazard — never deploy a pre-made marker to a fresh server.
  4. Confirm dev-only files did not leak into the release. Build/CI scaffolding, the deploy script, and the test suite must not exist on the server release — their presence means the export filter is misconfigured.

    Terminal window
    # On the launch-candidate server, inside the current release dir — all should be absent
    SSH_LAUNCH_ALIAS="<launch-candidate-alias-from-Zaj-PROJECT>"
    ssh "$SSH_LAUNCH_ALIAS" 'cd ~/domains/<DOMAIN>/deploy/current && for f in Admin-Local .github deploy.php tests; do
    [ -e "$f" ] && echo "LEAKED: $f (must not exist on the server release)" || echo "OK: $f absent"
    done'
    grep -nE 'export-ignore' .gitattributes 2>/dev/null || true
    # Expected: every entry reports OK: … absent; export-ignore covers local-only paths
    • ✅ Every denylist entry reports OK: … absent. If any leaked, add the path to .gitattributes with export-ignore (or your deploy exclude list), redeploy, and re-run:
    .gitattributes
    # .gitattributes — keep dev scaffolding out of the production export
    /Admin-Local export-ignore
    /.github export-ignore
    /deploy.php export-ignore
    /tests export-ignore

    Also sweep other common dev artifacts:

    Terminal window
    ssh "$SSH_LAUNCH_ALIAS" 'cd ~/domains/<DOMAIN>/deploy/current && find . -maxdepth 3 \( \
    -name ".env.example" -o -name ".env.local" -o -name ".playwright-mcp" -o \
    -name "phpunit.xml" -o -name "playwright-report" -o -name "test-results" \) -print'
    # Expected: no dev-only files on the launch-candidate environment
    • ✅ The launch-candidate environment contains no test reports, local MCP state, unneeded examples, or dev-only harness files.

Validate config/route/view caching, boot the app, then run static analysis and the zero-tolerance security sweep: hardcoded secrets, SQL injection (DB::raw / whereRaw / selectRaw must be parameterized), mass-assignment guards on every model, CSRF on every POST form, audited raw-output ({!! !!}) sites, and — for a CodeCanyon app — a re-scan of the deployed vendor/ for any surviving nulled-license / phone-home backdoor signature.

  1. Cache config and run static analysis.

    Terminal window
    php artisan config:cache && php artisan config:clear
    ./vendor/bin/phpstan analyse --memory-limit=512M # Expected: ≤ 5 non-critical
    • ✅ Config caches and clears without error; PHPStan reports ≤ 5 non-critical findings.
  2. Re-confirm the deployed vendor/ carries no surviving backdoor signature. A CodeCanyon archive can ship a nulled-license / phone-home injection inside vendor/ (the WorkDo-style chr()-obfuscated beacon that fires on login/register). The Phase 2 vendor-seam audit stripped it locally — but a fresh build, a vendor update since then, or a deploy that copied a shipped vendor/ instead of running composer install --no-dev could have re-introduced it. Grep the launch-candidate deployed tree.

    Terminal window
    SSH_LAUNCH_ALIAS="<launch-candidate-alias-from-Zaj-PROJECT>"
    ssh "$SSH_LAUNCH_ALIAS" 'cd ~/domains/<DOMAIN>/deploy/current && \
    grep -rEl "getCourant|HeaderCodec|envato\.|verify\.js|getScript" vendor/ 2>/dev/null' \
    && echo " 🔴 backdoor signature PRESENT on launch candidate — STOP, do not advance" \
    || echo " ✅ no backdoor signature in deployed vendor/"
    • ✅ The deployed vendor/ returns zero signature hits. If any appear, the deployed tree is tampered — clean it before launch with composer reinstall <vendor>/<package> (or rm -rf vendor && composer install --no-dev); plain composer install is a no-op on an already-installed locked package and will not replace tampered files. Re-grep until clean. Re-run this exact check after every future vendor update and again on production in Phase 12.

Migrations are the source of truth — identical migration files, all applied, mean identical schemas. Verify migration parity across local and the launch-candidate environment from Zaj-PROJECT.md (same count, 0 pending, all applied), foreign-key integrity, zero orphaned records, and no unapproved demo/test data. Phase 12 repeats the same check against production after production exists.

  1. Check migration parity on every environment.

    Terminal window
    php artisan migrate:status # Expected: all "Ran", 0 pending — on every environment
    • ✅ Local and the launch-candidate environment show all migrations “Ran” with 0 pending, foreign keys intact, and no unapproved demo/test data.
  2. Run data-quality assertions against the live database. These catch “schema is fine, data is broken” launch failures.

    -- Run per environment; each query must return 0 rows (or 0 count) on the launch-candidate environment
    SELECT email, COUNT(*) c FROM users GROUP BY email HAVING c > 1; -- duplicate emails
    SELECT COUNT(*) FROM users WHERE email IS NULL OR name IS NULL; -- required-field NULLs
    SELECT COUNT(*) FROM users WHERE email LIKE '%@example.%' -- residual test/demo data
    OR email LIKE 'test%@%';
    Terminal window
    php artisan tinker --execute='foreach (["users","orders","subscriptions","payments"] as $t) { if (Schema::hasTable($t)) echo $t.": ".DB::table($t)->count().PHP_EOL; }'
    # Expected: counts make sense; no unapproved seed/test/demo customers remain on the launch candidate
    • ✅ Duplicate-email and NULL-violation queries return zero rows; the launch candidate carries no unapproved test rows; launch-critical required fields are populated.

On the launch-candidate environment, confirm the expected APP_ENV, APP_DEBUG=false, an APP_KEY starting with base64:, the correct APP_URL, and that .env was never committed. Verify payment mode matches the environment row (test/sandbox for non-production, live only if this row is production), SMTP sends, error-tracking captures, a valid TLS certificate, an HTTP→HTTPS 301, the full canonical security-header set from Phase 7 — all six (HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Content-Security-Policy, Permissions-Policy) at Grade A on securityheaders.com (not a subset; SSL Labs (TLS) A/A+ separately) — and the health endpoint. Phase 12 rechecks APP_ENV=production and live payment keys after production deploy.

  1. Confirm the HTTPS redirect and health endpoint.

    Terminal window
    curl -I http://<your-domain> # Expected: 301 → https
    curl -s -o /dev/null -w "%{http_code}" https://<your-domain>/up # Expected: 200
    • ✅ HTTP returns a 301 to HTTPS, /up returns 200, and the launch-candidate env values, payment mode, and headers are confirmed.

Walk the real user journeys against the launch-candidate environment with payment test/sandbox keys unless this row is already production: signup (target < 90s), email verification, login, core CRUD, payment success and decline, mobile on a physical device, profile/password changes, file upload, and password reset. The human performs account creation, login, and password-entry moments; the agent resumes after each required session exists. Then check API status codes, the browser console (no JS/CORS errors), data isolation between users, XSS/SQL-injection escaping in form fields, and login lockout.

  1. Walk the payment paths with the provider’s test cards.

    PurposeTest cardResult
    Success4242 4242 4242 4242Payment succeeds
    Decline4000 0000 0000 0002Card declined
    3D Secure4000 0025 0000 3155Authentication required
    • ✅ Each card produces its expected result, and every journey — human signup/login/password reset plus agent post-session CRUD, mobile, uploads, and checks — completes cleanly with no console errors and correct data isolation.
  2. Measure a performance baseline against numeric thresholds. “Feels fast” is not a gate; these are.

    Terminal window
    curl -s -w "Time: %{time_total}s\n" -o /dev/null "https://<your-domain>/"
    # Expected: under 5 s acceptable ceiling (NO-GO above ~10 s)
    MetricTarget
    Page load< 3 s ideal · < 5 s acceptable (NO-GO above ~10 s)
    API response< 500 ms
    PageSpeed — Mobile> 50
    PageSpeed — Desktop> 70
    PageSpeed — Best Practices> 80
    FlowLaunch threshold
    Signup → verified< 90 seconds excluding inbox delay
    Login → dashboard usable< 3 seconds on desktop broadband
    First core create action< 5 minutes from account creation
    Mobile checkout/sign-upNo horizontal scroll; no blocked CTA
    Critical API responses2xx/3xx expected; no hidden 4xx/5xx
    • ✅ Page load is under the acceptable ceiling, the API responds under 500 ms, the three PageSpeed scores clear their targets, and journey notes include timings — not just screenshots.

Confirm error tracking receives a deliberately thrown test exception within a minute, alert rules exist (new issue, high volume, critical), uptime monitors watch the homepage, login, and a /health endpoint, log rotation keeps 14+ days, and recent backups exist. Define alert thresholds (error rate, response time, disk) and trigger one test alert end-to-end.

  1. Expose a health endpoint that checks DB, cache, and queue connectivity.

    // routes/api.php — health endpoint checks DB, cache, and queue config
    Route::get('/health', function () {
    try {
    DB::connection()->getPdo();
    Cache::put('health_check', true, 10);
    $cache = Cache::get('health_check') === true ? 'ok' : 'error';
    $queue = config('queue.default') ?: 'unset';
    return response()->json([
    'status' => 'ok',
    'database' => 'connected',
    'cache' => $cache,
    'queue' => $queue,
    ]);
    } catch (\Exception $e) {
    return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
    }
    });
    • ✅ Error tracking captures a test exception within a minute, uptime monitors watch homepage/login//health, log rotation keeps 14+ days, and one end-to-end test alert fired.

Run the tiered go/no-go on the next page. Tier 1 must be 13/13 or the launch does not happen.

7b. Verify Cloudflare / CDN settings (SHOULD — non-blocking)

Section titled “7b. Verify Cloudflare / CDN settings (SHOULD — non-blocking)”

Confirm that the selected domain or prepared production zone’s SSL mode, TLS floor, HSTS, WAF rule count, and DNSSEC status match the values configured in Phase 4. This step reports drift only — a mismatch should be fixed before accepting live traffic, but the production-only recheck still happens in Phase 12 after production is deployed.

  1. Run the five Cloudflare API checks.

    Terminal window
    # Confirm token is set (skip API curls in an interactive shell until loaded)
    if [ -z "${CF_API_TOKEN:-}" ]; then
    echo "Token missing — load from secure vault (op run / op item get …), then re-run §1"
    else
    export CF_ZONE_ID=<your-zone-id>
    # SSL mode — expect: full or strict
    curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/ssl" \
    | jq -r '.result.value'
    # Min TLS version — expect: 1.2 or 1.3
    curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/min_tls_version" \
    | jq -r '.result.value'
    # HSTS — expect: {"enabled":true,...}
    curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/security_header" \
    | jq -r '.result.value.strict_transport_security'
    # WAF custom rule count — expect: matches Phase 4 count (typically 3+)
    curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/rulesets" \
    | jq '.result[] | select(.phase == "http_request_firewall_custom") | .rules | length'
    # DNSSEC status — expect: active
    curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dnssec" \
    | jq -r '.result.status'
    fi
    • ✅ SSL full or strict, TLS 1.2+, HSTS enabled, WAF rule count ≥ Phase 4 target, DNSSEC active when the production zone is already configured. Any mismatch → fix before first real user signup or carry it into Phase 12 Task P.0 as a blocker.

7c. Detect schema drift across environments (SHOULD)

Section titled “7c. Detect schema drift across environments (SHOULD)”

Compare local and the launch-candidate environment before first traffic. If production already exists, include production as a third snapshot; otherwise record a Phase 12 carry-forward so production schema parity is checked immediately after the first production installer/deploy. This is separate from migrate:status: two environments can both show “all ran” while manual SQL or installer behavior created drift.

  1. Method 1 — export and diff schema-only baselines. Herd Pro bundles MariaDB, so the local dump tool is mariadb-dump (mysqldump was renamed); the remote hosts usually still ship mysqldump. The snippet picks whichever exists per host.

    Terminal window
    DUMP=mariadb-dump; command -v "$DUMP" >/dev/null 2>&1 || DUMP="mysqldump"
    "$DUMP" --no-data --skip-comments <local-db> > /tmp/schema-local.sql
    # Remote hosts: try mariadb-dump first, fall back to mysqldump on the far side.
    ssh <SSH_LAUNCH_ALIAS> 'command -v mariadb-dump >/dev/null 2>&1 && D=mariadb-dump || D=mysqldump; "$D" --no-data --skip-comments <launch-candidate-db>' > /tmp/schema-launch.sql
    diff -u /tmp/schema-local.sql /tmp/schema-launch.sql | sed -n '1,220p'
    # If production already exists, add the third snapshot now; otherwise Phase 12 owns it.
    if [ -n "${SSH_PRODUCTION_ALIAS:-}" ]; then
    ssh "$SSH_PRODUCTION_ALIAS" 'command -v mariadb-dump >/dev/null 2>&1 && D=mariadb-dump || D=mysqldump; "$D" --no-data --skip-comments <production-db>' > /tmp/schema-production.sql
    diff -u /tmp/schema-launch.sql /tmp/schema-production.sql | sed -n '1,220p'
    fi
    # Expected: empty diff except known charset/auto-increment noise that is documented.
    # (--no-data is read-only — it never touches the live schema or rows.)
    • ✅ Any schema drift is explained, fixed through migrations, or recorded as an intentional vendor/install difference before launch.
  2. Method 2 — run Atlas CLI as a cross-check. Load database URLs from your vault or environment first; never inline passwords in commands, docs, shell history, or screenshots.

    Terminal window
    atlas schema inspect -u "$LOCAL_DB_URL" --format '{{ sql . }}' > /tmp/local.sql
    atlas schema inspect -u "$LAUNCH_DB_URL" --format '{{ sql . }}' > /tmp/launch.sql
    diff /tmp/local.sql /tmp/launch.sql
    # Optional now, required in Phase 12 once production exists:
    if [ -n "${PROD_DB_URL:-}" ]; then
    atlas schema inspect -u "$PROD_DB_URL" --format '{{ sql . }}' > /tmp/prod.sql
    diff /tmp/launch.sql /tmp/prod.sql
    fi
    # Expected: empty diff except known charset/auto-increment noise that is documented

    Interpret the direction carefully: if any server has extra columns that local and the launch-candidate environment do not have, someone likely ran raw SQL. Investigate before launch. If mysqldump and Atlas disagree, trust Atlas and keep digging until the difference is explained.

  1. Hand off to the tiered signoff. Confirm Tier 1 is 13/13 on the next page before opening registration.

    • ✅ Tier 1 reads 13/13, so the launch is cleared to proceed.

Do not mark this step done until every box below is checked.

  • 🤖 Git & deploy verified — clean tree, current symlink correct, scheduler entry present, public/storage (+ public/packages) resolve on the launch-candidate environment, installer marker present in shared storage and not git-tracked.
  • 🤖 Audit deferrals transferred — latest Phase 10 consolidated verdict exists, is GO or accepted CONDITIONAL GO, and every 📋 Deferred to Zaj-BACKLOG.md row is in root Zaj-BACKLOG.md.
  • 🤖 Security sweep clean — zero vulnerable instances; PHPStan ≤ 5 non-critical; deployed vendor/ shows zero backdoor signatures (getCourant/HeaderCodec/envato./verify.js/getScript).
  • 🤖 Database confidence — migrations at parity, 0 pending, no unapproved test/demo data on the launch-candidate environment; production parity is carried into Phase 12 if production does not exist yet.
  • 🤖 Env audit passedAPP_DEBUG=false, base64: key, HTTPS 301, security headers, and environment-appropriate payment mode.
  • 🔀 End-to-end journeys pass — payment test cards clear; 👤 mobile verified on a physical device.
  • 🤖 Monitoring live — test exception captured, uptime monitors green, backups recent.
  • 🤖 Infra re-verified (SHOULD) — Cloudflare SSL/TLS/HSTS/WAF/DNSSEC match Phase 4 where the zone exists (step 7b); SEO sitemap.xml + robots.txt reachable and SPF/DKIM/DMARC re-checked. Production-only drift is carried into Phase 12 Task P.0 as a blocker before live traffic.
  • 🤖 Signoff ready — Tier 1 confirmed 13/13 on the next page.