diff --git a/SESSION_LOG/2026-02-28-modrinth-install-integration.md b/SESSION_LOG/2026-02-28-modrinth-install-integration.md new file mode 100644 index 0000000..97e7818 --- /dev/null +++ b/SESSION_LOG/2026-02-28-modrinth-install-integration.md @@ -0,0 +1,154 @@ +# 2026-02-28 – Modrinth Install + Full Mod Lifecycle + +**Type:** Foundational Architecture + Implementation +**Status:** Full mod lifecycle complete, file browser next + +--- + +## Completed + +### API + +Implemented full mod lifecycle routes: + +``` +GET /api/game/mods/search?q=&vmid= +GET /api/game/servers/:id/mods +POST /api/game/servers/:id/mods/install +PATCH /api/game/servers/:id/mods/:modId +DELETE /api/game/servers/:id/mods/:modId +``` + +All routes: +- `requireAuth` +- Enforce container ownership +- Enforce `ctype === "game"` +- Forward to agent with timeout + `502`/`504` mapping + +### Resolver + +- Loader normalization fixed (`neoforge` / `fabric` / `forge` / `quilt`) +- `.jar`-only enforcement +- Host allowlist: `cdn.modrinth.com` +- Publish-date safe sorting +- SHA512 preferred (fallback to SHA1) + +### Agent + +Mod install flow: + +- Accepts direct Modrinth artifact URL +- Validates: + - HTTPS only + - Allowed host (`cdn.modrinth.com`, `artifacts.zerolaghub.com`) + - Max size 200MB + - SHA256 hash +- Writes to `/mods` +- Enables via filename convention +- Soft delete moves to `/mods-removed` +- Enable/disable via rename: `.jar` ↔ `.jar.disabled` + +### Frontend + +- Mod search drawer implemented +- Installed mods panel implemented +- Installed flag merged into search results (heuristic matching) +- Install button functional +- Enable / disable functional +- Delete functional +- Toast notifications added + +--- + +## Current System Behavior + +Filesystem is source of truth. + +No database persistence for: +- Mod install history +- Modrinth project ID mapping + +Installed matching is heuristic based on: +- Slug +- Filename +- Name + +Soft delete retains file permanently in `/mods-removed`. No retention policy implemented (intentional). + +--- + +## Known Architectural Tradeoffs + +- No deterministic Modrinth project ID persistence yet +- Installed detection is best-effort heuristic +- No install queue +- No auto-update logic + +--- + +## Security Controls In Place + +**Agent level:** +- HTTPS-only downloads +- Host allowlist enforcement +- Redirect limit (max 3) +- 200MB hard cap +- SHA256 verification +- Filename sanitization (`[a-zA-Z0-9.+\-]` — `+` added for Modrinth filenames) +- Duplicate prevention +- Ownership enforcement (`minecraft:minecraft`) +- Temp file cleanup on failure +- Controlled mod directory write path +- Cache invalidation after every mutation + +**API level:** +- Auth + ownership enforcement +- VMID validation +- Timeout protection with `AbortController` +- Resolver filtering (loader + version + stability) +- Correct payload contract to agent + +--- + +## Payload Contract (API → Agent) + +```json +{ + "source": "modrinth", + "download_url": "...", + "filename": "...", + "sha512": "..." +} +``` + +Engine metadata format (corrected this session): + +``` +engineType = "neoforge" (not "minecraft") +engineVersion = "1.21.4" (not "neoforge-1.21.4") +``` + +--- + +## Remaining Issues + +### Response Corruption (Needs Verification) + +One early `curl` output appeared corrupted. Reproduce before wiring portal: + +```bash +curl -sSl -D headers.txt -o body.txt ... +``` + +### API Error Mapping Refinement + +- `"mod already exists"` should return `409` (currently `502`) + +--- + +## Next Steps + +1. Reproduce and resolve response corruption issue +2. Tighten API error mapping (`409` for duplicate) +3. Wire install endpoint to portal UI +4. Begin file browser (see `OPEN_THREADS.md`)