Operator-experience release. Correctness of the merge engine is settled from
1.0.x; this one focuses on observability, validation, admin workflow, and
external integrations.
Added
Per-file merge provenance log. Every output path now records which pack wrote it, every contributor (in merge order), the merge strategy used, and whether it was a true merge vs single-contributor pass-through. Persisted to output/<merged-pack>.zip.provenance.json so restarts don't blank the state. Exposed via PackMerger.getLastMergeProvenance().
/pm inspect collisions — list of every path touched by 2+ packs
/pm inspect export — writes the full plain-text report to output/last-merge-report.txt
Plugin API + Bukkit events. New sh.pcx.packmerger.api.PackMergerApi interface accessible via plugin.getApi() with accessors for the current pack URL, SHA-1, last merge time, and provenance, plus triggerMerge(). Six new Bukkit events fire at their natural call sites: PackMergeStartedEvent, PackMergedEvent, PackUploadedEvent, PackUploadFailedEvent, PackValidationFailedEvent, PackSentToPlayerEvent. See docs/api-example.java for a sample listener. Tagged @Experimental until 1.2 to allow iteration in 1.1.x.
pack_format vs server-version guardrail. PackValidator now warns when the merged pack's pack_format doesn't match the running Minecraft version. Honors supported_formats ranges (int, array, and {min_inclusive, max_inclusive} object shapes). Config key: validation.pack-format-check (warn | error | off).
Rollback on validation failure. Merges now write to <output>.new.zip first; validation runs against the temp, and the previous output is kept live if validation errors trip. Fires PackValidationFailedEvent with rolledBack=true so listeners can page someone. Config: validation.rollback-on-errors (default true) and validation.fail-on-warnings (default false).
Orphan asset detection. New OrphanDetector scans the merged output for unreferenced .png and .ogg files and reports them as warnings. Reference discovery covers models, item definitions, blockstates, atlases (with directory glob expansion), font providers, and sounds.json. Config: validation.detect-orphans (default true) and validation.orphan-report-limit (default 20).
/pm priority in-game reordering. No more YAML edit + reload: /pm priority list|up|down|top|bottom|set <pack> <n>. Persists via plugin.saveConfig() and triggers an immediate re-merge. Note: Bukkit's config writer doesn't preserve comments on save — documented tradeoff.
Profiles / presets. New profiles: + active-profile: config keys let operators flip between whole pack compositions atomically via /pm profile switch <name>. When profiles aren't in use (or the section is absent), PackMerger falls back to root-level keys — fully backwards- compatible with 1.0.x configs.
Remote pack sources (HTTP). New remote-packs: config section lets admins declare pack aliases whose contents come from an HTTP(S) URL. Packs are downloaded into packs/.remote-cache/<alias>.zip and recognized in the merge pipeline by their alias. ETag / Last-Modified caching, env-var substitution for secrets, bearer + basic auth, HTTPS- required-by-default. New /pm fetch [alias] command. Zero new runtime deps — uses the JDK's HttpClient.
S3-compatible upload provider. New provider: "s3" setting with support for AWS S3, Cloudflare R2, and Backblaze B2 (all via the S3 API). Bundled MinIO SDK, fully shaded + relocated. Supports content-addressed or stable key strategies, public-read or presigned (private) ACLs, and a retention policy for content-addressed buckets. Jar grows from ~270 KB to ~13 MB as a result; document in release notes.
Two new PluginLogger categories: validation() (light purple) and remote() (blue).
Runtime dependency loader. New PackMergerLoader downloads MinIO and its transitive closure from Maven Central on first enable, verifies each artifact against a build-time SHA-256, and loads them through an isolated classloader. Cached in plugins/PackMerger/libraries/ for subsequent starts. Drops the shipped jar from ~13 MB to ~360 KB.
Update check. On enable, the plugin polls versions.json in the repo to see if a newer release is available and surfaces it in the console + as a chat notice to admins on join. Advisory only — no auto-download. Config: update-check.enabled / update-check.url.
Actual Folia support. Swapped every BukkitScheduler call to the right Folia scheduler (AsyncScheduler for periodic async work, GlobalRegionScheduler / entity scheduler where a specific thread matters). PackDistributor.sendPack now self-schedules on the player's region so callers don't have to know about threading. plugin.yml declares folia-supported: true. Paper behavior is unchanged — the scheduler APIs we use exist on both.
Changed
PackMergeEngine.merge() now accepts an optional target-file override (merge(File)), used by PackMerger for the write-to-temp-then-validate flow. Backwards-compatible: merge() keeps writing to plugin.getOutputFile().
Merge provenance moved from a single fixed-name file (output/.merge-provenance.json) to a sidecar keyed by output name (output/<merged-pack>.zip.provenance.json). Lets rollback rename the zip and sidecar together.
FileWatcher now explicitly ignores dot-prefixed entries so the .remote-cache/ subdirectory can't trigger cascading merges.
Dependencies
MinIO Java SDK 8.5.10 + its transitive closure (OkHttp, Okio, Kotlin stdlib, Jackson, BouncyCastle, Guava, Xerial Snappy) is now downloaded at runtime by the loader rather than shaded into the plugin jar. Manifest and SHA-256 digests live in RuntimeDependencies.java (auto-generated at build time from the runtimeDownload Gradle configuration).