Audit KMP artifact readiness for MiniPay integration (#1838)

* update specs

* update specs

* pr feedback
This commit is contained in:
Justin Hernandez
2026-03-10 20:10:43 -07:00
committed by GitHub
parent 1041babc7b
commit 6dcaa63de3
4 changed files with 213 additions and 44 deletions

View File

@@ -78,15 +78,17 @@
## Backlog
| ID | Title | Status | Priority | Depends On | Plan | PR |
| ----- | ------------------------------------------------------------- | -------- | -------- | ---------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| NS-01 | Physical-device validation matrix for Android + iOS NFC flows | Done | High | - | [plans/NS-01-physical-device-validation.md](./plans/NS-01-physical-device-validation.md) | - |
| NS-02 | iOS Camera MRZ Phase 2 | Deferred | Medium | NS-01 | - | - |
| NS-03 | Publishing readiness for AAR + XCFramework artifacts | Ready | High | NS-01 | [plans/NS-03-publishing-readiness.md](./plans/NS-03-publishing-readiness.md) | - |
| NS-04 | APDU allowlist in KMP NFC bridge handler | Ready | High | - | [plans/NS-04-apdu-allowlist.md](./plans/NS-04-apdu-allowlist.md) | - |
| NS-05 | LifecycleBridgeHandler type/error semantics on iOS | Ready | Low | - | [plans/NS-05-lifecycle-handler-semantics.md](./plans/NS-05-lifecycle-handler-semantics.md) | - |
| NS-06 | Align KMP callback/result contract with canonical SDK types | Done | Medium | NS-01 | [plans/NS-06-kmp-callback-contract-alignment.md](./plans/NS-06-kmp-callback-contract-alignment.md) | - |
| NS-07 | Remove legacy `{ type }` shim from native lifecycle handlers | Blocked | Medium | - | - | Blocked on WebView bundle emitting canonical outcomes instead of flat `{ type }` payloads. Requires coordinated change: update `ConfirmIdentificationScreen.tsx` and `ProvingScreen.tsx` to send canonical `VerificationResult`, then remove the `type != null` branch from both Android and iOS `LifecycleBridgeHandler`. |
| ID | Title | Status | Priority | Depends On | Plan | PR |
| ----- | ------------------------------------------------------------- | -------- | -------- | ------------ | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| NS-01 | Physical-device validation matrix for Android + iOS NFC flows | Done | High | - | [plans/NS-01-physical-device-validation.md](./plans/NS-01-physical-device-validation.md) | - |
| NS-02 | iOS Camera MRZ Phase 2 | Deferred | Medium | NS-01 | - | - |
| NS-03 | Publishing readiness for AAR + XCFramework artifacts | Done | High | NS-01 | [plans/NS-03-publishing-readiness.md](./plans/NS-03-publishing-readiness.md) | N/A (audit-only) |
| NS-04 | APDU allowlist in KMP NFC bridge handler | Ready | High | - | [plans/NS-04-apdu-allowlist.md](./plans/NS-04-apdu-allowlist.md) | - |
| NS-05 | LifecycleBridgeHandler type/error semantics on iOS | Ready | Low | - | [plans/NS-05-lifecycle-handler-semantics.md](./plans/NS-05-lifecycle-handler-semantics.md) | - |
| NS-06 | Align KMP callback/result contract with canonical SDK types | Done | Medium | NS-01 | [plans/NS-06-kmp-callback-contract-alignment.md](./plans/NS-06-kmp-callback-contract-alignment.md) | - |
| NS-07 | Remove legacy `{ type }` shim from native lifecycle handlers | Blocked | Medium | - | - | Blocked on WebView bundle emitting canonical outcomes instead of flat `{ type }` payloads. Requires coordinated change: update `ConfirmIdentificationScreen.tsx` and `ProvingScreen.tsx` to send canonical `VerificationResult`, then remove the `type != null` branch from both Android and iOS `LifecycleBridgeHandler`. |
| NS-08 | Ship AAR + XCFramework to MiniPay | Blocked | High | NS-03, NS-09 | [plans/NS-08-ship-artifacts-to-minipay.md](./plans/NS-08-ship-artifacts-to-minipay.md) | ~10 LOC Gradle config for Maven repo target, switch XCFramework to release variants, host XCFramework for SPM. iOS handoff blocked until NS-09 resolves the private NFCPassportReader dependency. |
| NS-09 | Make NFCPassportReader fork accessible to external consumers | Ready | High | - | [plans/NS-09-nfcpassportreader-distribution.md](./plans/NS-09-nfcpassportreader-distribution.md) | Decision: make fork public or vendor it. Blocks any external iOS consumer. |
Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done`
@@ -95,10 +97,12 @@ Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done`
| Plan | IDs | Status |
| -------------------------------------------------------------------------------------------------- | ----- | ------ |
| [plans/NS-01-physical-device-validation.md](./plans/NS-01-physical-device-validation.md) | NS-01 | Done |
| [plans/NS-03-publishing-readiness.md](./plans/NS-03-publishing-readiness.md) | NS-03 | Ready |
| [plans/NS-03-publishing-readiness.md](./plans/NS-03-publishing-readiness.md) | NS-03 | Done |
| [plans/NS-04-apdu-allowlist.md](./plans/NS-04-apdu-allowlist.md) | NS-04 | Ready |
| [plans/NS-05-lifecycle-handler-semantics.md](./plans/NS-05-lifecycle-handler-semantics.md) | NS-05 | Ready |
| [plans/NS-06-kmp-callback-contract-alignment.md](./plans/NS-06-kmp-callback-contract-alignment.md) | NS-06 | Done |
| [plans/NS-08-ship-artifacts-to-minipay.md](./plans/NS-08-ship-artifacts-to-minipay.md) | NS-08 | Ready |
| [plans/NS-09-nfcpassportreader-distribution.md](./plans/NS-09-nfcpassportreader-distribution.md) | NS-09 | Ready |
## Completion Checklist
@@ -112,6 +116,7 @@ Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done`
- `NS-01` completed on 2026-03-10 after operator-assisted real-device NFC validation confirmed Android and iOS success and failure paths in the KMP test app. See [plans/NS-01-physical-device-validation.md](./plans/NS-01-physical-device-validation.md) for the validation log.
- `NS-06` completed on 2026-03-10. KMP now exposes the canonical `VerificationResult` shape (`success`, `userId`, `verificationId`, `proof`, `claims`, `error`), and `claims` now carries heterogeneous values via `Map<String, Any?>`.
- Flat lifecycle `{ type }` payloads remain supported as an internal compatibility shim while the embedded WebView bundle still emits them, but KMP host apps no longer receive a public `VerificationResult.type` field. Tracked as `NS-07` for removal once the WebView sends canonical outcomes.
- `NS-03` completed on 2026-03-10. Audit validated AAR and XCFramework generation. Four items block shipping to MiniPay: Maven repo target (~10 LOC config), release XCFramework variants (~3 LOC), hosted XCFramework for SPM, and NFCPassportReader fork accessibility. Tracked as NS-08 and NS-09.
## Overview

View File

@@ -1,67 +1,130 @@
# Publishing Readiness for AAR and XCFramework
> Last updated: 2026-03-10
> Status: Ready
> Status: Done (audit complete)
- Workstream: native-shells
- Backlog IDs: NS-03
- Owner: Native Shells
- Branch: TBD
- PR: TBD
- Branch: N/A (audit-only, no code changes)
- PR: N/A
## Why
## Goal
- `Production publishing (npm + AAR + XCFramework)` is still open at the SDK level.
- Packaging gaps are easy to lose track of because implementation and release concerns are split across multiple specs.
- This plan isolates artifact-readiness work from feature work.
## Scope
- Audit current AAR and XCFramework generation paths.
- Define missing packaging metadata, versioning, release inputs, and validation steps.
- Update the relevant specs so release readiness is tracked in one place.
Answer one question: **can MiniPay (or another host app) integrate the KMP SDK today?** Document what works, what doesn't, and the shortest path to a shippable artifact.
## Out of Scope
- Performing the actual release/publish step.
- Enterprise publishing (Maven Central signing, full POM compliance, CocoaPods).
- RN npm publishing.
- Feature changes to handlers or bridge contracts.
## Files to Modify
---
- `specs/projects/sdk/workstreams/native-shells/SPEC.md`
- `specs/projects/sdk/OVERVIEW.md`
- packaging/handoff docs if needed
## Audit Results
## Files Not to Modify
### Android AAR
- runtime handler implementations unless packaging audit exposes a required packaging-only change
**Status: Builds and publishes to local Maven. Not yet configured for a remote repository.**
## Preconditions
**Validated (ran locally on 2026-03-10):**
- Physical-device validation should be complete or explicitly waived.
- Versioning/release ownership is identified.
| Step | Command | Result |
| ---------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Release AAR | `./gradlew :shared:assembleRelease` | `shared/build/outputs/aar/shared-release.aar` |
| Maven local publish | `./gradlew :shared:publishToMavenLocal` | Installs AAR + POM + sources + Gradle metadata to `~/.m2/repository/xyz/self/sdk/shared-android/0.1.0/` |
| POM transitive deps | Inspected generated POM | Declares kotlin-stdlib, webkit, jmrtd, bouncycastle, mlkit, camera, biometrics with correct scopes |
| WebView asset bundling | `copyWebViewAssets` (runs on preBuild) | Copies `webview-app/dist/` into Android assets |
## Implementation Notes
**Not validated:** Consumer resolution (a separate Gradle project resolving `xyz.self.sdk:shared-android:0.1.0` from mavenLocal and compiling against it). The POM and Gradle metadata look correct but this hasn't been tested end-to-end from a clean consumer project.
- Keep this focused on release readiness, not implementation cleanup.
- If packaging requires code changes, split those into child backlog IDs or follow-up plans.
**To ship to MiniPay:**
## Validation
1. Add a `publishing { repositories {} }` block pointing to GitHub Packages (or any Maven repo MiniPay can reach). ~10 lines of Gradle config.
2. Bump version from `0.1.0` when ready.
3. Ideally, validate consumer resolution from a clean project before the first external handoff.
**Later (not blocking):** ProGuard consumer rules, CI publish job, Maven Central migration if needed.
### iOS KMP Framework / XCFramework
**Status: Builds. Shippable via local SPM path or manual XCFramework handoff.**
**What works:**
| Step | Command | Result |
| ------------------- | ------------------------------------------------------- | ------------------------------------------------------ |
| XCFramework | `./gradlew createXCFramework` | `SelfSdk.xcframework` (arm64 device + arm64 simulator) |
| SPM Package.swift | Exists | Points to local XCFramework binary target |
| Simulator framework | `./gradlew :shared:linkDebugFrameworkIosSimulatorArm64` | Works |
**To ship to an iOS host app:**
1. Switch `createXCFramework` from debug to release framework variants. In `build.gradle.kts`, change `linkDebugFrameworkIos*``linkReleaseFrameworkIos*` in the `createXCFramework` task's `dependsOn` and path strings (~3 lines).
2. Build: `./gradlew createXCFramework``shared/build/xcframework/SelfSdk.xcframework`.
3. Zip + checksum: `zip -r SelfSdk-0.1.0.xcframework.zip SelfSdk.xcframework && swift package compute-checksum SelfSdk-0.1.0.xcframework.zip`.
4. Upload the zip to a GitHub Release (e.g., `kmp-sdk@0.1.0`) or any HTTPS-reachable location.
5. Update `packages/kmp-sdk/Package.swift` to use `.binaryTarget(name: "SelfSdk", url: "<release-url>", checksum: "<sha256>")` instead of the local path.
6. Tag the release. SPM consumers add the repo URL with the version tag.
**Ownership:** Whoever cuts the release owns steps 26. Version naming follows `packages/kmp-sdk/shared/build.gradle.kts` `version` field. Git tag format: `kmp-sdk@<version>`.
**Not yet decided:** Whether the host app consumes `SelfSdk.xcframework` (KMP) directly and adds `self-sdk-swift` as a separate SPM dependency, or whether a wrapper package bundles both. Current default: two separate dependencies — KMP XCFramework + Swift companion SPM package. This is fine for MiniPay (single known consumer) but should be revisited if the consumer count grows.
**Later (not blocking):** x86_64 simulator slice (Intel Macs), CocoaPods podspec, CI automation.
### Swift Companion Package (`self-sdk-swift`)
**Status: Works in Xcode and on real devices. `swift build` CLI fails (irrelevant for iOS distribution).**
**What works:**
- CI builds via `xcodebuild` for iOS Simulator
- Real-device NFC validated through KMP test app
- Providers wire into KMP iOS via `SdkProviderRegistry`
**Known issue:** `swift build` fails because OpenSSL headers don't resolve under SPM CLI on macOS. This doesn't affect iOS distribution (always goes through Xcode).
**To ship:**
1. The `NFCPassportReader` fork is private (`git@github.com:selfxyz/NFCPassportReader.git`). Any external consumer needs access. Simplest fix: make the fork public, or vendor it.
2. That's the only real blocker. Everything else (versioning, CI) is polish.
---
## Summary: What Blocks Shipping
| What | Effort | Blocks |
| ----------------------------------------------------------- | -------------- | ------------------------------- |
| Add Maven repository target to Gradle | ~10 LOC config | Android distribution to MiniPay |
| Switch XCFramework to release variants | ~3 LOC config | iOS production builds |
| Host XCFramework somewhere reachable + update Package.swift | Small | iOS SPM distribution |
| Make NFCPassportReader fork accessible | Decision | Any external iOS consumer |
Everything else (Maven Central, GPG signing, full POM metadata, ProGuard rules, x86_64 slices, CocoaPods, CI publish jobs, version automation) is real work but doesn't block getting artifacts into MiniPay's hands.
---
## Validated Commands
```bash
cd packages/kmp-sdk && ./gradlew :shared:assembleDebug
cd packages/kmp-sdk && ./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
cd packages/self-sdk-swift && swift build
cd packages/kmp-sdk && ./gradlew :shared:assembleRelease # ✅ AAR
cd packages/kmp-sdk && ./gradlew :shared:publishToMavenLocal # ✅ AAR + POM + sources + metadata to ~/.m2
cd packages/kmp-sdk && ./gradlew :shared:linkDebugFrameworkIosSimulatorArm64 # ✅ iOS framework
cd packages/kmp-sdk && ./gradlew createXCFramework # ✅ XCFramework
cd packages/self-sdk-swift && swift build # ❌ Known OpenSSL header issue (not a distribution blocker)
```
**Not yet validated:** Consumer resolution from a clean Gradle project. POM and metadata look correct but no end-to-end consume test has been run.
## Definition of Done
- [ ] AAR generation path documented and validated
- [ ] XCFramework or framework packaging path documented and validated
- [ ] Remaining blockers explicitly listed
- [ ] Backlog row updated
- [x] AAR generation path documented and validated
- [x] XCFramework or framework packaging path documented and validated
- [x] Remaining blockers explicitly listed
- [x] Backlog row updated
## Status Log
- 2026-03-10: Created from SDK publishing follow-up.
- 2026-03-10: Audit complete. Artifacts build. Four items block shipping to external consumers; all are small config or decision items.

View File

@@ -0,0 +1,59 @@
# Ship AAR + XCFramework to MiniPay
> Last updated: 2026-03-10
> Status: Ready
- Workstream: native-shells
- Backlog IDs: NS-08
- Owner: Native Shells
- Depends on: NS-03
- Branch: TBD
- PR: TBD
## Goal
Make KMP SDK artifacts consumable by MiniPay. Android via Maven repository, iOS via SPM with hosted XCFramework.
## Scope
### Android
1. Add `publishing { repositories { maven { ... } } }` block to `shared/build.gradle.kts` targeting GitHub Packages (or whichever Maven repo MiniPay can resolve).
2. Validate: run `./gradlew :shared:publish`, then resolve `xyz.self.sdk:shared-android:0.1.0` from a clean consumer Gradle project.
### iOS
1. Switch `createXCFramework` task from debug to release variants (~3 lines in `build.gradle.kts`).
2. Build, zip, compute checksum, upload to GitHub Release.
3. Update `Package.swift` binary target from local path to release URL + checksum.
4. Tag release. Validate: add SPM dependency from a clean Xcode project.
## Files to Modify
- `packages/kmp-sdk/shared/build.gradle.kts` — add publishing repository block
- `packages/kmp-sdk/shared/build.gradle.kts` — switch `createXCFramework` to release variants
- `packages/kmp-sdk/Package.swift` — remote binary target URL
## Out of Scope
- Maven Central (GPG signing, full POM metadata). GitHub Packages or equivalent is sufficient.
- CocoaPods. SPM is sufficient for MiniPay.
- CI automation for publishing. Manual release is fine for now.
## Validation
```bash
# Android
cd packages/kmp-sdk && ./gradlew :shared:publish
# Then from a separate consumer project: resolve xyz.self.sdk:shared-android:<version>
# iOS
cd packages/kmp-sdk && ./gradlew createXCFramework
# Then from a clean Xcode project: add SPM dependency and build
```
## Definition of Done
- [ ] MiniPay can resolve the Android AAR from a Maven repository
- [ ] MiniPay can add the iOS XCFramework via SPM from a hosted URL
- [ ] Consumer resolution validated end-to-end on both platforms

View File

@@ -0,0 +1,42 @@
# NFCPassportReader Distribution Strategy
> Last updated: 2026-03-10
> Status: Ready
- Workstream: native-shells
- Backlog IDs: NS-09
- Owner: Native Shells
- Branch: TBD
- PR: TBD
## Goal
Make `self-sdk-swift` consumable by external iOS host apps. The blocker is the private `NFCPassportReader` fork dependency.
## Problem
`self-sdk-swift/Package.swift` depends on `git@github.com:selfxyz/NFCPassportReader.git` (SSH, private fork). Any external consumer needs GitHub SSH access to the selfxyz org to resolve this dependency. MiniPay (or any host app outside the org) can't build without it.
## Options
| Option | Effort | Trade-off |
| ----------------------------------------------------- | ------ | ------------------------------------------------------------------------------- |
| Make the fork public | Low | Exposes fork changes; may have upstream license implications to check |
| Vendor NFCPassportReader source into `self-sdk-swift` | Medium | No external dependency; increases repo size; must manually sync upstream |
| Pre-build NFCPassportReader as XCFramework binary | Medium | Consumers get a binary; but adds a build/release step for the dependency itself |
| Switch Package.swift to HTTPS + access token | Low | Still requires credentials; doesn't solve the external consumer problem |
## Recommendation
Make the fork public unless there's a specific reason it's private. Lowest effort, solves the problem completely.
## Scope
1. Decide on approach (decision, not code).
2. Execute the chosen option.
3. If the fork remains an external dependency, update `packages/self-sdk-swift/Package.swift` from the SSH URL to an HTTPS URL and pin a tag/revision that external consumers can resolve.
4. Validate: `self-sdk-swift` resolves from a clean environment without org SSH credentials.
## Definition of Done
- [ ] An external iOS consumer can resolve all `self-sdk-swift` dependencies without selfxyz org credentials