diff --git a/specs/projects/sdk/workstreams/kmp-revival/SPEC.md b/specs/projects/sdk/workstreams/kmp-revival/SPEC.md index b68e9b00b..da33abbd7 100644 --- a/specs/projects/sdk/workstreams/kmp-revival/SPEC.md +++ b/specs/projects/sdk/workstreams/kmp-revival/SPEC.md @@ -133,3 +133,4 @@ Any other domain request returns a `DOMAIN_NOT_FOUND` error response. | [Native Shells Lite](../native-shells-lite/SPEC.md) | Sibling — serves non-KMP consumers | | [Paused Native Shells (KMP)](../../paused/native-shells/SPEC.md) | Historical KMP work — validated foundation | | [Build Pipeline](../build-pipeline/SPEC.md) | Downstream — bundles webview-app into native assets | +| [SDK Distribution — SD-06](../sdk-distribution/SPEC.md) | Downstream — remote publishing after KR-03 validates artifacts | diff --git a/specs/projects/sdk/workstreams/kmp-revival/plans/KR-03-validate-and-publish.md b/specs/projects/sdk/workstreams/kmp-revival/plans/KR-03-validate-and-publish.md index 4529411e2..048859b37 100644 --- a/specs/projects/sdk/workstreams/kmp-revival/plans/KR-03-validate-and-publish.md +++ b/specs/projects/sdk/workstreams/kmp-revival/plans/KR-03-validate-and-publish.md @@ -21,7 +21,7 @@ After KR-01 (Android parity) and KR-02 (iOS parity), you need to validate that t ### Out of Scope -- Actual publishing to external Maven/SPM registries (that's a follow-up, equivalent to paused NS-08) +- Actual publishing to external Maven/SPM registries (tracked as [SD-06](../../sdk-distribution/plans/SD-06-kmp-remote-publishing.md)) - `packages/native-shell-android/` and `packages/native-shell-ios/` — do not modify - CI/CD pipeline changes - Android dependency trimming or Android manifest slimming inside `packages/kmp-sdk/` — that ownership belongs to KR-01; KR-03 validates the result diff --git a/specs/projects/sdk/workstreams/sdk-distribution/SPEC.md b/specs/projects/sdk/workstreams/sdk-distribution/SPEC.md index 25fd93a4d..bcb255869 100644 --- a/specs/projects/sdk/workstreams/sdk-distribution/SPEC.md +++ b/specs/projects/sdk/workstreams/sdk-distribution/SPEC.md @@ -1,6 +1,6 @@ # SDK Distribution — Implementation Spec -> Last updated: 2026-03-30 +> Last updated: 2026-04-06 > Owner: SDK / Platform > Parent: `../../OVERVIEW.md` > Status: Active @@ -44,6 +44,7 @@ | Native Shells (Lite) — NSL-02 | Upstream | Active | iOS shell must exist before switching URL loading | | `packages/webview-app/` | Upstream | Active | Source of the hosted web app | | Build Pipeline | Sibling | Active | Bundle script remains for local dev only after SD-03 | +| KMP Revival — KR-03 | Upstream | Active | KMP artifacts validated locally before remote publish | ## Ownership Boundaries @@ -52,6 +53,7 @@ | `packages/native-shell-android/` | SDK Distribution | Config + URL loading changes only | | `packages/native-shell-ios/` | SDK Distribution | Config + URL loading changes only | | `packages/webview-app/` | SDK Distribution | Hosting setup only (no source changes) | +| `packages/kmp-sdk/` | SDK Distribution | Publishing config only (SD-06) | | Bridge handlers | Native Shells | Not modified by this workstream | ## Backlog @@ -63,6 +65,7 @@ | SD-03 | WebView app hosting setup | Ready | High | — | [plans/SD-03-hosting-setup.md](./plans/SD-03-hosting-setup.md) | — | | SD-04 | Android Maven publishing | Ready | Medium | SD-01 | [plans/SD-04-android-maven-publishing.md](./plans/SD-04-android-maven-publishing.md) | — | | SD-05 | iOS publishing (SPM + CocoaPods) | Ready | Medium | SD-02 | [plans/SD-05-ios-spm-publishing.md](./plans/SD-05-ios-spm-publishing.md) | — | +| SD-06 | KMP remote publishing (Maven+SPM) | Ready | Medium | KR-03 | [plans/SD-06-kmp-remote-publishing.md](./plans/SD-06-kmp-remote-publishing.md) | — | Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` @@ -70,7 +73,7 @@ Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` 1. **SD-03** first — hosting must be live before native shells can load it 2. **SD-01 + SD-02** in parallel — Android and iOS URL loading changes -3. **SD-04 + SD-05** in parallel — publishing cleanup after URL loading works +3. **SD-04 + SD-05 + SD-06** in parallel — all publishing (SD-06 runs once KR-03 completes) ## Active Plans @@ -81,6 +84,7 @@ Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` | [plans/SD-03-hosting-setup.md](./plans/SD-03-hosting-setup.md) | SD-03 | Ready | | [plans/SD-04-android-maven-publishing.md](./plans/SD-04-android-maven-publishing.md) | SD-04 | Ready | | [plans/SD-05-ios-spm-publishing.md](./plans/SD-05-ios-spm-publishing.md) | SD-05 | Ready | +| [plans/SD-06-kmp-remote-publishing.md](./plans/SD-06-kmp-remote-publishing.md) | SD-06 | Ready | ## Completion Checklist @@ -108,3 +112,4 @@ Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` | [Build Pipeline](../build-pipeline/SPEC.md) | Sibling — bundle script retained for local dev only | | [WebView Spec](../webview/SPEC.md) | Upstream — produces the web app being hosted | | [SDK Core Spec](../sdk-core/SPEC.md) | Sibling — engine consumed by hosted web app | +| [KMP Revival](../kmp-revival/SPEC.md) | Upstream — KR-03 validates artifacts before SD-06 publishes | diff --git a/specs/projects/sdk/workstreams/sdk-distribution/plans/SD-06-kmp-remote-publishing.md b/specs/projects/sdk/workstreams/sdk-distribution/plans/SD-06-kmp-remote-publishing.md new file mode 100644 index 000000000..2a04c889d --- /dev/null +++ b/specs/projects/sdk/workstreams/sdk-distribution/plans/SD-06-kmp-remote-publishing.md @@ -0,0 +1,238 @@ +## KMP Remote Publishing (Maven + SPM) + +> Last updated: 2026-04-06 +> Status: Ready + +- Workstream: sdk-distribution +- Backlog IDs: SD-06 +- Owner: TBD +- Branch: TBD +- PR: TBD + +### Why + +The KMP SDK (`packages/kmp-sdk/`) already has `maven-publish` configured and produces both AAR and XCFramework artifacts (validated by KR-03). What's missing is the remote publishing configuration: a Maven repository target for Android consumers and a hosted XCFramework URL for iOS/SPM consumers. Without this, integrators must build from source or use local artifacts. + +### Scope + +- Add remote Maven repository configuration to `packages/kmp-sdk/shared/build.gradle.kts` +- Switch `createXCFramework` task from debug to release variants +- Update `packages/kmp-sdk/Package.swift` from local path to remote URL + checksum +- Add `publish-kmp-sdk.yml` workflow (manual dispatch with dry-run, matching SD-04 pattern) + +### Out of Scope + +- Maven Central account setup / GPG signing (ops task, not code) +- CDN or hosting infrastructure for XCFramework ZIP +- KMP source code changes (owned by kmp-revival workstream) +- Native shell publishing (SD-04, SD-05) +- NFC / biometric handler registration or dependency changes +- NFCPassportReader fork accessibility (known limitation, does not block 3-domain distribution) + +### Preconditions + +- KR-03 complete (build artifacts validated locally, `publishToMavenLocal` succeeds, XCFramework builds) + +### Files to Modify + +- `packages/kmp-sdk/shared/build.gradle.kts` — Add `publishing { repositories { maven { ... } } }` block for remote Maven repo. Switch `createXCFramework` task dependencies from `linkDebugFramework*` to `linkReleaseFramework*` and update framework paths from `debugFramework` to `releaseFramework`. +- `packages/kmp-sdk/Package.swift` — Change `.binaryTarget` from local `path:` to remote `url:` + `checksum:`. +- `.github/workflows/publish-kmp-sdk.yml` — Add manual dispatch workflow with dry-run mode (matching SD-04 pattern). + +### Files NOT to Modify + +- `packages/kmp-sdk/shared/src/` — No source code changes +- `packages/native-shell-android/` — Separate publishing (SD-04) +- `packages/native-shell-ios/` — Separate publishing (SD-05) +- `packages/webview-app/` — Upstream, do not change +- `packages/self-sdk-swift/` — iOS provider package, unchanged + +### Implementation Details + +#### 1. Add remote Maven repository configuration + +**File:** `packages/kmp-sdk/shared/build.gradle.kts` + +The `maven-publish` plugin is already applied (line 7). `publishLibraryVariants("release")` is already set (line 17). The Gradle module name defaults to `shared` (the module directory name), which would publish as `xyz.self.sdk:shared`. Override the `artifactId` to `self-sdk-kmp` so the final Maven coordinate is `xyz.self.sdk:self-sdk-kmp:0.1.0`. Add a `publishing` block with the artifactId override and remote repository target: + +```kotlin +publishing { + publications.withType { + artifactId = "self-sdk-kmp" + } + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/selfxyz/self") + credentials { + username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.token") as String? ?: System.getenv("GITHUB_TOKEN") + } + } + } +} +``` + +This adds ~13 LOC. The `groupId` (`xyz.self.sdk`) and `version` (`0.1.0`) are already set at lines 10-11. The published Maven coordinate will be `xyz.self.sdk:self-sdk-kmp:0.1.0`. + +#### 2. Switch XCFramework to release variants + +**File:** `packages/kmp-sdk/shared/build.gradle.kts` + +Update the `createXCFramework` task (lines 145-175): + +- Change `dependsOn` from `linkDebugFrameworkIosArm64` / `linkDebugFrameworkIosSimulatorArm64` to `linkReleaseFrameworkIosArm64` / `linkReleaseFrameworkIosSimulatorArm64` +- Update framework paths from `debugFramework` to `releaseFramework` + +```kotlin +tasks.register("createXCFramework") { + group = "build" + description = "Creates XCFramework for iOS distribution" + + dependsOn( + ":shared:linkReleaseFrameworkIosArm64", + ":shared:linkReleaseFrameworkIosSimulatorArm64", + ) + + doLast { + val buildDir = layout.buildDirectory.get().asFile + val frameworkPath = "$buildDir/bin/iosArm64/releaseFramework/SelfSdk.framework" + val simulatorFrameworkPath = "$buildDir/bin/iosSimulatorArm64/releaseFramework/SelfSdk.framework" + val xcframeworkPath = "$buildDir/xcframework/SelfSdk.xcframework" + + // Remove existing XCFramework if present + project.delete(xcframeworkPath) + + project.exec { + commandLine( + "xcodebuild", + "-create-xcframework", + "-framework", frameworkPath, + "-framework", simulatorFrameworkPath, + "-output", xcframeworkPath, + ) + } + + println("XCFramework created at: $xcframeworkPath") + } +} +``` + +~3 LOC changed (debug → release in 3 places). + +#### 3. Update Package.swift for remote distribution + +**File:** `packages/kmp-sdk/Package.swift` + +Change the `.binaryTarget` from a local path to a remote URL with checksum: + +```swift +targets: [ + .binaryTarget( + name: "SelfSdk", + url: "https://github.com/selfxyz/self/releases/download/kmp-sdk-v0.1.0/SelfSdk.xcframework.zip", + checksum: "" + ) +] +``` + +The checksum is computed from the zipped XCFramework: + +```bash +cd packages/kmp-sdk +zip -r SelfSdk.xcframework.zip shared/build/xcframework/SelfSdk.xcframework +swift package compute-checksum SelfSdk.xcframework.zip +``` + +The actual URL and checksum values will be set during the first release. Use placeholder values with a `// TODO: Update on first release` comment until then. + +#### 4. Add publish workflow + +**File:** `.github/workflows/publish-kmp-sdk.yml` + +Match the SD-04 pattern (`publish-android-sdk.yml`): manual `workflow_dispatch` with `version` input and `dry-run` toggle (defaults to on). Steps: checkout → setup JDK 17 → cache Gradle → set version → build → test (`./gradlew :shared:jvmTest`) → publish (`publishToMavenLocal` for dry-run, `publishAllPublicationsToGitHubPackagesRepository` for real). + +### Validation + +```bash +cd packages/kmp-sdk + +# Verify remote Maven repo is configured +./gradlew tasks --all | grep -i publish +# Should show publishAllPublicationsToGitHubPackagesRepository (or similar) + +# Verify release XCFramework builds +./gradlew createXCFramework +ls -la shared/build/xcframework/SelfSdk.xcframework + +# Verify Package.swift is valid +swift package dump-package + +# Existing tests still pass +./gradlew :shared:jvmTest +``` + +### Consumer Setup + +The Maven package on GitHub Packages requires authentication even though the repo is public. Consumers need a GitHub token with `read:packages` scope (any GitHub account works — no org/repo access required). + +#### Android (Gradle) + +Add the GitHub Packages Maven repository to `settings.gradle.kts` (or root `build.gradle.kts`): + +```kotlin +dependencyResolutionManagement { + repositories { + maven { + url = uri("https://maven.pkg.github.com/selfxyz/self") + credentials { + username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.token") as String? ?: System.getenv("GITHUB_TOKEN") + } + } + } +} +``` + +Then add the dependency: + +```kotlin +implementation("xyz.self.sdk:self-sdk-kmp:0.1.0") +``` + +Consumers need a GitHub personal access token with `read:packages` scope. Set in `~/.gradle/gradle.properties`: + +```properties +gpr.user=GITHUB_USERNAME +gpr.token=ghp_YOUR_TOKEN +``` + +Or set `GITHUB_ACTOR` / `GITHUB_TOKEN` environment variables in CI. + +#### iOS (SPM) + +The XCFramework ZIP hosted on GitHub Releases can also be private. If the repo is private, consumers must authenticate for `swift package resolve` to download the binary target. Xcode handles this via the logged-in GitHub account in **Xcode > Settings > Accounts**. CI environments need a `netrc` entry or `GITHUB_TOKEN` for authentication. + +### Known Limitations + +- **GitHub Packages Maven:** Consumers need a GitHub token with `read:packages` scope to resolve dependencies (see Consumer Setup above). If public access is required, migration to Maven Central is a follow-up. + +### Definition of Done + +- [ ] `publishing` block added to `build.gradle.kts` with `artifactId = "self-sdk-kmp"` and GitHub Packages repository +- [ ] `createXCFramework` uses release variants (not debug) +- [ ] `Package.swift` uses `.binaryTarget(url:checksum:)` instead of `path:` +- [ ] `./gradlew :shared:jvmTest` passes +- [ ] `./gradlew createXCFramework` produces a release XCFramework +- [ ] `swift package dump-package` succeeds +- [ ] `publish-kmp-sdk.yml` workflow added with dry-run mode +- [ ] Backlog row updated +- [ ] Plan status updated + +### Estimated PR Size + +~50-80 LOC changed. Well within the 1k-3k target. + +### Status Log + +- 2026-04-06: Plan created. Based on NS-03 audit findings and KR-03 deferred publishing scope.