# MSG-2026-04-13-truth-file-version-detection **From:** Chronicler #85 **Date:** 2026-04-13 **Priority:** HIGH — needed before BuiltByBit listings, ideally before April 15 **Status:** OPEN ## Background After 3 rounds of Gemini consultation and live Wings API testing, we have a complete architectural decision for version detection. Here's everything you need to implement it. --- ## What We Proved via Live Testing - `DaemonFileRepository::getContent()` and `putContent()` both work from Panel PHP context (confirmed via tinker on live panel) - `manifest.json` does NOT survive Pterodactyl modpack installation on any of our 22 servers — installer discards it - `modpack_installations` table has no version info — only provider + modpack_id - Wings directory listing works — we can read the server filesystem --- ## The Fix: Truth File Strategy ### New Detection Order (replaces current fall-through logic) ``` 1. DB: current_file_id present? → skip to comparison 2. Wings: read /.modpack-checker.json → parse file_id → store in DB → compare 3. Wings: read /manifest.json → parse file_id → write Truth File → store in DB → compare 4. Nothing found → set status = 'pending_calibration' → STOP (never seed from latest) ``` **CRITICAL RULE: Never seed current_version from latest API result.** If we don't know the installed version, we say we don't know. Store `null` / `pending_calibration`, never assume latest = installed. --- ## The Truth File Write `.modpack-checker.json` to server root via `DaemonFileRepository::putContent()`. ```json { "extension": "modpackchecker", "project_id": "490660", "file_id": "7097953", "version": "5.10.15", "calibrated_at": "2026-04-13T05:00:00+00:00" } ``` **Write the Truth File in TWO scenarios:** 1. When admin calibrates (picks version from dropdown) 2. When ANY detection method successfully finds the version (manifest.json etc.) This makes detection self-healing — once tracked by any method, always tracked even if the original source file disappears in a future update. --- ## New DB Status Value Add `pending_calibration` to the status enum in `modpackchecker_servers`: ```sql ALTER TABLE modpackchecker_servers MODIFY COLUMN status ENUM( 'up_to_date','update_available','error','unknown','pending_calibration' ) NOT NULL DEFAULT 'unknown'; ``` --- ## Files to Change ### 1. `app/Console/Commands/CheckModpackUpdates.php` In `checkVersion()` — replace the seeding fallback with Truth File logic: ```php // After existing detection chain (egg var, existing DB)... // NEW: Check for Truth File on server filesystem if (empty($currentVersion)) { try { $repo = app(\Pterodactyl\Repositories\Wings\DaemonFileRepository::class) ->setServer($server); $truthFile = json_decode($repo->getContent('/.modpack-checker.json'), true); if (!empty($truthFile['file_id'])) { $currentVersion = $truthFile['version'] ?? null; $currentFileId = $truthFile['file_id']; } } catch (\Exception $e) { // File doesn't exist — continue to next check } } // NEW: Check for legacy manifest.json if (empty($currentVersion)) { try { $repo = $repo ?? app(\Pterodactyl\Repositories\Wings\DaemonFileRepository::class) ->setServer($server); $manifest = json_decode($repo->getContent('/manifest.json'), true); if (!empty($manifest['files'][0]['fileID'])) { $currentFileId = (string) $manifest['files'][0]['fileID']; // Write Truth File immediately so it persists $this->writeTruthFile($server, $modpackId, $currentFileId, null); } } catch (\Exception $e) { // File doesn't exist — continue } } // REMOVE the seeding fallback entirely. Replace with: if (empty($currentVersion) && empty($currentFileId)) { $this->updateDatabase($server, [ 'platform' => $platform, 'modpack_id' => $modpackId, 'modpack_name' => $latestData['name'], 'status' => 'pending_calibration', 'detection_method' => $method, 'last_checked' => now(), ]); $this->info(" ⏳ PENDING: {$latestData['name']} — calibration required"); return; } ``` Add a `writeTruthFile()` helper method: ```php private function writeTruthFile(Server $server, string $projectId, string $fileId, ?string $version): void { try { $repo = app(\Pterodactyl\Repositories\Wings\DaemonFileRepository::class) ->setServer($server); $repo->putContent('/.modpack-checker.json', json_encode([ 'extension' => 'modpackchecker', 'project_id' => $projectId, 'file_id' => $fileId, 'version' => $version, 'calibrated_at' => now()->toIso8601String(), ], JSON_PRETTY_PRINT)); } catch (\Exception $e) { // Non-fatal — log and continue \Log::warning('[MVC] Could not write Truth File: ' . $e->getMessage()); } } ``` ### 2. `app/Http/Controllers/ModpackAPIController.php` In the `calibrate()` method — after storing the file_id in DB, write the Truth File: ```php // After DB update in calibrate()... $this->writeTruthFileForServer($server, $modpackId, $fileId, $version); ``` Add the same `writeTruthFile` logic (or extract to a shared service). ### 3. `views/server/wrapper.tsx` Add `pending_calibration` handling. When `data.configured === false` and status is pending, show calibration prompt instead of empty/error state: ```tsx // After error state check, before main render... if (!data.configured && !data.update_available) { return (
{data.modpack_name || 'Modpack'} — Version unknown
); } ``` ### 4. Ignore toggle — Muted Card (from previous Gemini consultation) Replace `if (data.is_ignored) return null;` with: ```tsx if (data.is_ignored) { return (
{data.modpack_name || 'Modpack'} — Updates ignored
); } ``` --- ## Migration Needed ```sql ALTER TABLE modpackchecker_servers MODIFY COLUMN status ENUM( 'up_to_date','update_available','error','unknown','pending_calibration' ) NOT NULL DEFAULT 'unknown'; ``` Add to a new migration file: `database/migrations/2026_04_13_000002_add_pending_calibration_status.php` --- ## Post-Launch Enhancement (NOT for April 15) Log parsing at server restart — possible future detection method. Every modpack formats startup logs differently, too brittle for now. Flag as Task for after launch. --- ## Testing Checklist - [ ] Server with no Truth File → shows "Identify Version" button - [ ] Admin selects version → Truth File written to server root → DB updated - [ ] Cron reads Truth File on next run → correct status shown - [ ] Server shows update available after Truth File written with old version - [ ] Ignore toggle → muted card with Resume button (not vanish) - [ ] Resume → normal card restored - [ ] Never seeds current_version from latest API result --- *— Chronicler #85*