# Contributing To ajni

## Scope

`ajni` is a JNI runtime whose core target is a checked, bidirectional Java↔MoonBit call path. Android host integration remains a first consumer; WebView is optional and must not shape the core interface. Keep JNI ownership, Kotlin host behavior, and MoonBit public API explicit.

The root `Nanaloveyuki/ajni` package must remain usable without linking the optional `Nanaloveyuki/ajni/webview` native stub. General JNI runtime work follows [ADR 0001](docs/adr/0001-general-jni.md): the core owns session, handle, method/field, exception, and callback contracts; desktop JVM invocation and Android host adapters stay separate.

Discuss changes that alter JNI class names, Kotlin/native method signatures, public MoonBit APIs, Android API levels, or the feature split before starting implementation. Focused fixes, tests, and documentation improvements can be proposed directly.

Before MoonBit 1.0, preserve the existing behavior while the general JNI seam is designed and implemented incrementally. Do not add binding generators, new platform/ABI support, or additional WebView features as part of this work. Use the ADR records for the runtime surface and `CHANGELOG.md` for historical defect evidence.

## Development Setup

Install toolchain archive `0.10.19+97801ee1b`, recorded in `.moonbit-toolchain`, JDK 21 for JVM smoke and Java 17 for Android builds.
Its `moon` CLI version is `0.1.20261029`, recorded in `.moon-version`; archive IDs are compiler versions,
not CLI versions. For Linux/macOS, pass the contents of `.moonbit-toolchain` to the official
installer. On Windows, set `MOONBIT_INSTALL_VERSION` to the same archive ID before running
the official PowerShell installer. Run `moon update` once to fetch the registry index; module
dependencies then resolve during `moon check` or `moon build`, because `moon install` only
installs global binary packages and requires an explicit `<SOURCE>`. `moon package --list
--frozen` inspects the already-synced module directory and cannot fetch anything itself, so
run it after a check or build.

The root pins also apply to `tests/jvm`, `tests/invocation`, and the unpublished `tests/ohos`
module. Android build work additionally needs Android SDK platform 35,
platform tools, NDK `29.0.14206865`, CMake `4.1.2`, and the bundled Gradle
`8.10.2` wrapper.

Set `ANDROID_NDK_HOME` when the NDK is not discoverable through Android Studio:

```powershell
$env:ANDROID_NDK_HOME = "C:\path\to\android-ndk-r29"
```

The repository does not vendor Android SDK, NDK, Gradle caches, MoonBit build
output, or generated APKs.

## Package Boundaries

- `src/` is the descriptor/text package; `src/runtime/` owns generic JNI resources
  and calls. `src/unsafe/` exposes raw host integration; `src/invocation/` alone
  imports dynlib. Do not add browser commands or event types to these packages.
- `src/webview/` owns the optional WebView MoonBit API and
  `ajni_webview_bridge.c` native stub.
- `android/host/` contains the stable Kotlin JNI host class
  `dev.nanaloveyuki.ajni.host.NativeBridge`.
- `android/app/` is a demo consumer, not the public host contract.

WebView changes must update the MoonBit feature package, its JNI stub, Kotlin
host, CMake feature linkage, package docs, and focused tests together. Preserve
the core-only build path.

## Validate Changes

Run native static checks and build-only existing unit tests:

```powershell
moon fmt --check
moon check --target native
moon test --target native --build-only
moon info --target native
python3 scripts/check-package-files.py
```

For Android JNI, Kotlin host, CMake, or ABI fixes, compile the demo only when
the required SDK/NDK/build tools are available; do not treat compilation as a
platform runtime test:

```powershell
.\scripts\build-android.ps1
```

For the full static Android acceptance, set `ANDROID_HOME`, `ANDROID_NDK_HOME`
and `MOON_HOME`, then run:

```sh
./android/gradlew -p android :host:checkJniContract :host:lintDebug :host:assembleDebug \
  :app:assembleDebug :app:assembleDebugAndroidTest :app:lintDebug --no-daemon --stacktrace
```

`checkJniContract` depends on typed `compileDebugKotlin` and its output provider,
uses actual bytecode without class initialization, and compiles temporary TUs
including real stubs with NDK API24 drivers (`arm64-v8a`/`x86_64`, production and
exports-disabled callback branches). The standalone CLI requires `--class-dir`,
`--ndk`, `--moon-home`; exit 2 means missing prerequisites, exit 1 a contract,
parsing or compilation failure. Do not substitute host headers or fake refcounts.
JNI varargs expression types still need manual review; keep rules do not prove
R8-minified lookup. Checker smoke may mutate temporary copies/captured javap,
never production source. Package checks use `moon package --list --frozen` after
dependency installation, consume both output streams, and fail on missing inputs.

Run `moon update` then `python3 scripts/run-jvm-smoke.py` on Linux with JDK21
and a C compiler. Its isolated workspace exercises Java/native hosting against
the real checked JVM, including typed calls, callbacks, lifetime and failures.
This is distinct from Kotlin/ART evidence. CI executes this fixture, while
Android CI compiles both ABIs/instrumentation and runs static lint without devices.
When a device/emulator is available, run instrumentation and record its API/ABI
and actual results separately. A build or Kotlin-only observer is not JNI proof.
Non-Android feature operations report unsupported, never simulated platform success.

Keep observer mutation/main-thread contracts and snapshot reentrancy intact;
subscriptions survive ordinary shutdown. Instrumentation cleanup must clear the
observer, shut down, and reverse-detach tracked containers even after failures.
The Kotlin observer fires before JNI and is not end-to-end MoonBit evidence.
Current exercised checks and deferred scenarios are recorded in ADR 0001 for
JNI/Android and ADR 0002 for the Harmony fixture.

For the isolated Harmony NAPI fixture, run `python3 scripts/build-ohos-mvp.py --host-smoke`
with the installed DevEco SDK, then `python3 scripts/run-ohos-mvp.py --hap <hap>`
against one connected HDC target. Generated C and matching MoonBit runtime sources
are staged outside the repository. Keep SDK family/API/ABI build evidence separate
from emulator/device results; see [ADR 0002](docs/adr/0002-harmony-validation.md).

Update `README.md`, package `README.mbt.md` files, generated public interfaces,
and tests whenever observable behavior changes. Do not commit `_build`,
`.mooncakes`, Android SDK/NDK files, Gradle caches, or IDE output.

## Pull Requests

- Branch from current `main` and keep each PR focused.
- Describe the JNI/Kotlin/MoonBit surface affected and list validation run.
- Include the Android API and ABI scope for native changes.
- Keep the core and WebView feature boundary intact.
- All required GitHub Actions checks must pass.
- A pull request targeting `main` requires at least one developer LGTM before
  merge.

## CI Infrastructure

`.github/actions/setup-moonbit` installs the `.moonbit-toolchain` compiler archive on Linux,
verifies both the `.moon-version` CLI pin and exact compiler version, and primes the
registry index with `moon update`. Module dependencies resolve during `moon check`, so
the setup does not call `moon install`; that command only installs global binaries.
The Gradle job pins `gradle/actions/setup-gradle@v6` with `cache-provider: basic`: v6's
default `enhanced` provider loads the proprietary `gradle-actions-caching` component,
while the basic provider keeps CI on the MIT-licensed GitHub Actions cache.
`Build` separates MoonBit/package checks from Android JNI/lint/build checks;
the always-running `ci` job fails if either job fails, is cancelled, or is skipped.
Configure branch protection to require `ci` (replacing the former `checks` job)
and at least one review. Workflows do not alter repository protection settings.
Pull requests use read-only permissions and Gradle caches; only a release job
receives `contents: write`. Weekly Dependabot updates cover GitHub Actions only,
not the deliberately pinned compiler, Gradle, AGP or Android toolchain.

Artifacts from successful jobs:

| Name | Contents | Retention |
| --- | --- | --- |
| `ajni-package` | `moon package --frozen` source ZIP | 30 days |
| `ajni-android-debug` | Demo APK, instrumentation APK, host AAR | 30 days |
| `ajni-api-docs` | `moon doc --frozen` API data, not a static site | 14 days |
| `ajni-android-reports` | Available Android reports, also uploaded on failure | 14 days |

## Release Process

1. Update `moon.mod` and add a nonempty `## X.Y.Z` entry to `CHANGELOG.md` in a
   reviewed PR. Use stable numeric versions; prerelease tags are not supported.
2. After merging to `main`, validate and push the matching new tag:

   ```sh
   python3 scripts/check-release.py --tag vX.Y.Z
   git tag -a vX.Y.Z -m "ajni X.Y.Z"
   git push origin vX.Y.Z
   ```

3. `Release` validates metadata and `main` ancestry, calls the same Build workflow,
   downloads its artifacts, and creates a GitHub Release containing the MoonBit
   source ZIP, `ajni-android-debug-vX.Y.Z.zip`, and `SHA256SUMS`. No additional
   GitHub secret is needed. Do not reuse an existing release tag; failed checks
   can be rerun, but existing releases are not updated or overwritten.

The Android ZIP preserves `app/build/outputs/...` and `host/build/outputs/...`.
The host AAR ships Kotlin classes and consumer keep rules; native `libajni.so`
is integrated separately through the bundled CMake template, not embedded in
that AAR. Debug APKs use the runner's debug key and are not production releases.
CI success proves the documented static/build gates only, never device runtime.

### Optional Mooncakes Publishing

GitHub Releases work without a registry credential. To additionally publish the
same validated source ZIP to Mooncakes after GitHub Release creation:

1. Log in to the intended Mooncakes owner account on a trusted workstation using
   `moon login`. The official [package publishing guide](https://docs.moonbitlang.com/en/latest/toolchain/moon/package-manage-tour.html#setup-mooncakes-io-account)
   records the credential location as `~/.moon/credentials.json`.
2. Store that complete JSON file as the repository Actions secret
   `MOONCAKES_CREDENTIALS`, not a GitHub PAT or a bare token. Set the repository
   Actions variable `MOONCAKES_PUBLISH` to `true` to enable the job. The JSON shape
   is `{"token":"...","username":"..."}`; `username` is optional.
3. The release job writes credentials with mode `0600` only for publishing,
   extracts the validated source artifact, resolves dependencies with `moon check`,
   validates its publication file list, then runs `moon publish`. The pinned
   publisher rechecks a fresh extraction, so `--frozen` cannot sync its dependencies.
   `.moonignore` must ship with the archive to preserve both toolchain pins on repack.
   Credentials are removed on exit and in an always-running cleanup step; no
   secret is exposed to pull requests or cached/uploaded with MoonBit home.

Configure without printing the credential (replace the path when `MOON_HOME`
differs from the default):

```sh
gh secret set MOONCAKES_CREDENTIALS --repo Nanaloveyuki/ajni < "$HOME/.moon/credentials.json"
gh variable set MOONCAKES_PUBLISH --repo Nanaloveyuki/ajni --body true
```

This follows the official [MoonBit async publishing workflow](https://github.com/moonbitlang/async/blob/main/.github/workflows/publish.yml).
When disabled the registry job is skipped; when enabled, missing/malformed
credentials fail it rather than silently skipping publication. A failed registry
job leaves the GitHub Release intact; rerun failed jobs after correcting the
credential. If the registry already accepted the version, do not republish it.
Local smoke validates schema/path/permissions/cleanup with synthetic credentials
only; actual registry acceptance requires the owner's real secret and a new tag.
