Context
#463 shipped the Plugin bundle format with two okio-backed source factories: PluginBundleSource.fromDirectory (commonMain) and PluginBundleSource.fromZipFile (jvmMain). The split is forced by okio 3.11: FileSystem.openZip is declared in jvmMain only, so fromZipFile cannot live in commonMain today.
The practical impact is that iOS and Native consumers must unpack a ZIP bundle to a directory before importing it. This is acceptable for W0/W1 because the marketplace (W1.10) and share-sheet import (W1.11) currently target Desktop/Android first, both of which run on the JVM target. Once iOS or Native becomes a first-class import surface, the unpack step becomes user-visible friction we should remove.
Current state
| File |
Purpose |
ampere-core/src/commonMain/kotlin/link/socket/ampere/bundle/OkioPluginBundleSource.kt |
OkioPluginBundleSource class + PluginBundleSource.fromDirectory extension. Already commonMain — no work needed beyond the move below. |
ampere-core/src/jvmMain/kotlin/link/socket/ampere/bundle/PluginBundleSource.jvm.kt |
PluginBundleSource.fromZipFile extension that calls FileSystem.openZip. This file is the entirety of the platform split. |
ampere-core/src/jvmTest/kotlin/link/socket/ampere/bundle/OkioPluginBundleSourceTest.kt |
Includes ZIP fixtures built with java.util.zip.ZipOutputStream. JVM-only ZIP construction means these tests can't move to commonTest as-is. |
Preconditions for picking this up
Do not start work until at least one of these is true. If none hold, the ticket is still blocked even if the schedule says it's ready.
- Multiplatform
openZip is available. Track square/okio#1163 and the okio changelog. The trigger is a release where FileSystem.openZip is declared in commonMain (or in iosMain + jvmMain such that both targets can call it without a jvmMain shim). Verify by reading the release notes and confirming no expect/actual is needed in this project.
- A vetted multiplatform alternative is adopted. A KMP-friendly ZIP reader (e.g. a future
kotlinx-io-zip, or a vendored minimal reader in this repo) covers JVM, Android, iOS, and Native with read-only entry enumeration and byte access. "Vetted" means: a security review of the unzip path (zip-slip, zip-bomb, deflate-bomb) has been completed and documented.
- A concrete iOS or Native consumer exists. The marketplace UI or share-sheet importer on iOS/Native is actively shipping and the unpack-to-directory step has been measured to be a real ergonomic or performance problem. This guards against doing the work speculatively.
If only (3) is true and (1)/(2) are not, the right next step is not this ticket — open a separate spike to evaluate ZIP-reader options first.
Tasks
- Move
fromZipFile from jvmMain/.../bundle/PluginBundleSource.jvm.kt to commonMain/.../bundle/OkioPluginBundleSource.kt.
- Delete
jvmMain/.../bundle/PluginBundleSource.jvm.kt.
- Update the implementation to use whichever multiplatform ZIP API satisfied the precondition. If switching off okio, also update
OkioPluginBundleSource (or rename the file) to reflect the new backing.
- Update
docs/ampere/plugin-bundle-format.md: the "Reading a bundle" table currently lists fromZipFile as jvmMain — make it commonMain and drop the JVM-only caveat.
- Move the ZIP construction helper out of
OkioPluginBundleSourceTest into a commonTest fixture, OR add an iOS-side smoke test that loads a vendored .zip fixture committed under ampere-core/src/commonTest/resources/ (or platform-equivalent). Building ZIPs at test time on iOS is awkward; vendoring a small fixture is usually simpler.
- Add at least one iOS test that parses a ZIP bundle end-to-end via
PluginBundleSource.fromZipFile → PluginBundleParser.parse → BundleParseResult.Ok.
Validation
./gradlew jvmTest and ./gradlew iosSimulatorArm64Test (or whichever iOS test target the project is using) both pass with the moved factory.
- No file remains under
ampere-core/src/jvmMain/kotlin/link/socket/ampere/bundle/.
- The dir-vs-ZIP entry parity assertion in
OkioPluginBundleSourceTest (fromZipFile entries are keyed identically to fromDirectory) runs on both JVM and at least one non-JVM target.
- Spec doc no longer mentions a JVM-only ZIP path.
- No new dependencies were added without a security note in the PR description if (2) was the precondition that unblocked this work.
Context
#463 shipped the Plugin bundle format with two okio-backed source factories:
PluginBundleSource.fromDirectory(commonMain) andPluginBundleSource.fromZipFile(jvmMain). The split is forced by okio 3.11:FileSystem.openZipis declared in jvmMain only, sofromZipFilecannot live in commonMain today.The practical impact is that iOS and Native consumers must unpack a ZIP bundle to a directory before importing it. This is acceptable for W0/W1 because the marketplace (W1.10) and share-sheet import (W1.11) currently target Desktop/Android first, both of which run on the JVM target. Once iOS or Native becomes a first-class import surface, the unpack step becomes user-visible friction we should remove.
Current state
ampere-core/src/commonMain/kotlin/link/socket/ampere/bundle/OkioPluginBundleSource.ktOkioPluginBundleSourceclass +PluginBundleSource.fromDirectoryextension. Already commonMain — no work needed beyond the move below.ampere-core/src/jvmMain/kotlin/link/socket/ampere/bundle/PluginBundleSource.jvm.ktPluginBundleSource.fromZipFileextension that callsFileSystem.openZip. This file is the entirety of the platform split.ampere-core/src/jvmTest/kotlin/link/socket/ampere/bundle/OkioPluginBundleSourceTest.ktjava.util.zip.ZipOutputStream. JVM-only ZIP construction means these tests can't move to commonTest as-is.Preconditions for picking this up
Do not start work until at least one of these is true. If none hold, the ticket is still blocked even if the schedule says it's ready.
openZipis available. Track square/okio#1163 and the okio changelog. The trigger is a release whereFileSystem.openZipis declared in commonMain (or in iosMain + jvmMain such that both targets can call it without a jvmMain shim). Verify by reading the release notes and confirming noexpect/actualis needed in this project.kotlinx-io-zip, or a vendored minimal reader in this repo) covers JVM, Android, iOS, and Native with read-only entry enumeration and byte access. "Vetted" means: a security review of the unzip path (zip-slip, zip-bomb, deflate-bomb) has been completed and documented.If only (3) is true and (1)/(2) are not, the right next step is not this ticket — open a separate spike to evaluate ZIP-reader options first.
Tasks
fromZipFilefromjvmMain/.../bundle/PluginBundleSource.jvm.kttocommonMain/.../bundle/OkioPluginBundleSource.kt.jvmMain/.../bundle/PluginBundleSource.jvm.kt.OkioPluginBundleSource(or rename the file) to reflect the new backing.docs/ampere/plugin-bundle-format.md: the "Reading a bundle" table currently listsfromZipFileas jvmMain — make it commonMain and drop the JVM-only caveat.OkioPluginBundleSourceTestinto acommonTestfixture, OR add an iOS-side smoke test that loads a vendored.zipfixture committed underampere-core/src/commonTest/resources/(or platform-equivalent). Building ZIPs at test time on iOS is awkward; vendoring a small fixture is usually simpler.PluginBundleSource.fromZipFile→PluginBundleParser.parse→BundleParseResult.Ok.Validation
./gradlew jvmTestand./gradlew iosSimulatorArm64Test(or whichever iOS test target the project is using) both pass with the moved factory.ampere-core/src/jvmMain/kotlin/link/socket/ampere/bundle/.OkioPluginBundleSourceTest(fromZipFile entries are keyed identically to fromDirectory) runs on both JVM and at least one non-JVM target.