diff --git a/docs/code-bridge/responses/MSG-2026-04-13-truth-file-version-detection.md b/docs/code-bridge/responses/MSG-2026-04-13-truth-file-version-detection.md new file mode 100644 index 0000000..b00f076 --- /dev/null +++ b/docs/code-bridge/responses/MSG-2026-04-13-truth-file-version-detection.md @@ -0,0 +1,264 @@ +# 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 ( +