# Xaere Flutter receiver quickstart

This path keeps the mobile application, publisher backend and Xaere services
inside their intended trust boundaries. The Flutter app receives an opaque XR
frame and resolves it against Core. It contains no Xaere API key and no XR
Audience HMAC secret.

## 1. Add the preview packages

Download and extract the verified archive from
`https://sdk.xaere.io/downloads/xaere-flutter-sdk-0.3.28-preview-r3.tar.gz`.
Revision 2 preserves package version 0.3.28 and adds the Swift Package Manager
tree that was missing from the first distribution archive. CocoaPods and
SwiftPM compile the same reviewed iOS receiver sources.

Authenticate the immutable manifest and archive offline before extracting. Keep
a separately obtained copy of the public-key fingerprint
`sha256:8a800080f9463ec1d33cbf832a53913ddd0cb4afef51891de1e04ebc2a110ca7`;
downloading a key and fingerprint only from the same potentially compromised
host is not an independent trust decision:

```bash
curl -fsSLO https://sdk.xaere.io/releases/0.3.28-r3.json
curl -fsSLO https://sdk.xaere.io/releases/0.3.28-r3.signature.json
curl -fsSLO https://sdk.xaere.io/releases/xaere-sdk-release-ed25519.pem
curl -fsSLO https://sdk.xaere.io/tools/verify-sdk-release.mjs
curl -fsSLO https://sdk.xaere.io/downloads/xaere-flutter-sdk-0.3.28-preview-r3.tar.gz
node verify-sdk-release.mjs 0.3.28-r3.signature.json 0.3.28-r3.json \
  xaere-flutter-sdk-0.3.28-preview-r3.tar.gz xaere-sdk-release-ed25519.pem
```

The verifier checks the Ed25519 key identifier, signature over the exact
manifest bytes, archive byte length and archive SHA-256 before reporting
`"status":"verified"`.
Keep its two package directories together, then reference the Flutter adapter
from the host application's `pubspec.yaml`:

This preview requires Flutter 3.47 or newer and Dart 3.13 or newer. Its Android
example uses AGP 9 built-in Kotlin; host applications must not reapply the
legacy `kotlin-android` plugin to the Xaere module.

```yaml
dependencies:
  xaere_receiver_flutter:
    path: ../xaere-sdk/packages/xaere_receiver_flutter
```

The adapter resolves its sibling `xaere_receiver` package through the archive's
relative path. Never copy a server API key or Audience credential into this
file, mobile assets, Dart defines or source control.

## 2. Declare microphone use

Android merges `android.permission.RECORD_AUDIO` from the plugin manifest. An
iOS host must add a user-facing `NSMicrophoneUsageDescription` to `Info.plist`.
The pod already bundles a privacy manifest declaring no tracking, no collected
data and no required-reason API use. The host app must separately disclose any
data collection it adds around the SDK.
Request permission only from a visible user action. Returning from background
does not restart capture automatically.

## 3. Resolve only verified intents

```dart
final transport = XaereFlutterGgWaveTransport();
final receiver = XaereReceiver(
  transport: transport,
  resolver: XaereHttpResolver(Uri.parse('https://api.xaere.io')),
  consent: const XaerePrivacyConsent(
    audienceMeasurement: false,
    advertising: false,
    personalization: false,
  ),
);
final session = XaereFlutterReceiverSession(
  receiver: receiver,
  transport: transport,
);
final allowed = XaereHttpsUrlAllowList(['experience.publisher.example']);

final subscription = receiver.resolutions.listen(
  (resolution) {
    if (!allowed.allows(resolution.intent)) return;
    // Present the verified intent for host-app review before executing it.
  },
  onError: (Object error, StackTrace _) {
    if (error is XaereResolutionException) {
      // Map error.failure to bounded host UI. Never display error.toString()
      // or try to distinguish a revoked Code from an unknown Code.
    }
  },
);

// Call only from a visible Start action.
final started = await session.startFromUserAction();
// A visible Stop action calls await session.stop().
// Widget disposal cancels subscription and calls await session.close().
```

Core's short-lived Ed25519 envelope is verified locally before a resolution is
emitted. A valid signature proves Xaere issuance; the application allow-list
still decides which HTTPS destinations are safe for that host.

For failed HTTP resolution, `XaereResolutionException.failure` is a closed
host-safe enum. Only `rateLimited` and `serviceUnavailable` are retryable.
`codeUnavailable` deliberately combines unknown and revoked Codes, and the SDK
does not copy Core or gateway messages into the exception.

## 4. Keep Audience off until explicit consent

Resolution works with every optional purpose disabled. Keep Audience
measurement, advertising and personalization as three separate host choices;
never collapse them into one broad consent. The current SDK acts only on
`audienceMeasurement`. `advertising` and `personalization` are false by default
and deliberately have no capture, resolution or network behavior.

When the host has a valid Audience consent decision, it may call
`receiver.setConsent(...)`. If measurement is required, send the minimised
event only to the integrator-owned HTTPS backend using
`XaereBackendMeasurementReporter`. Do not point Flutter at
`ingest.audience.xaere.io`.

Configure the reporter when constructing the receiver. The authorization
callback must obtain a fresh, consent- and integration-bound token from the
publisher's normal authenticated session; it is not a constant, Dart define or
Xaere credential:

```dart
final reporter = XaereBackendMeasurementReporter(
  Uri.parse('https://backend.publisher.example/xaere/audience'),
  authorization: () => publisherSession.shortLivedXaereAudienceToken(),
);
final receiver = XaereReceiver(
  transport: transport,
  resolver: XaereHttpResolver(Uri.parse('https://api.xaere.io')),
  measurementReporter: reporter,
  consent: const XaerePrivacyConsent(audienceMeasurement: false),
);

// Keep the UI disabled until the reporter is actually present.
final canEnableAudience = receiver.audienceMeasurementAvailable;
```

Replace `publisherSession` with the host application's authenticated session.
The SDK rejects initial or updated Audience consent when no reporter is
configured, instead of silently accepting an ineffective choice. Close the
host-owned reporter when its application/session lifetime ends.

The integrator backend authenticates its own user, mints a consent- and
integration-bound authorization lasting at most five minutes, adds the Audience
HMAC, and forwards the closed event. Use the reviewed backend contract:
`https://docs.xaere.io/FLUTTER_BACKEND.md`.

The receiver independently rejects a Core Audience proof whose signed lifetime
exceeds five minutes or whose issue time is more than two minutes in the
future. This validation also applies before encrypted retry persistence and
after queue decryption.

The SDK considers delivery complete only after the backend returns exactly
HTTP `202` with JSON `{"accepted":true}`. Withdrawing consent cancels active
authorization and delivery, clears the optional encrypted retry queue and
rotates the in-memory measurement token.

## 5. Validate before release

Run Dart/Flutter analyses and tests, then the deterministic native diagnostic:

```dart
final codecReady = await transport.nativeCodecSelfTest();
```

Maintainers can run the complete Android bridge test on a connected emulator or
device with `tool/verify_android_plugin_runtime.ps1`. This proves Dart, Flutter,
Kotlin, JNI and ggwave linkage. It does not replace a physical
speaker-to-microphone test on representative Android and iOS phones.

Download the release-bound QA template and its standalone validator, keep the
completed evidence outside the source tree, replace every placeholder with
observations from the two real devices, and leave Audience measurement
disabled. The template is bound to immutable revision 3: the current Android
build-2 APK was rebuilt from it, and every meaningful iOS build-2 source file
was independently proven byte-identical to it. The template is intentionally
invalid until the physical observations are entered:

```bash
curl -fsSLo physical-acceptance.json https://sdk.xaere.io/releases/0.3.28-physical-acceptance.template.json
curl -fsSLo verify-physical-acceptance.mjs https://sdk.xaere.io/tools/verify-physical-acceptance.mjs
```

### Privacy-safe receiver diagnostics

The TestFlight example exposes `Copy privacy-safe diagnostics` after native
capture starts. The copied JSON is never uploaded automatically. It contains
the native platform, ggwave version, capture mode, sample rate, permission and
capture state, frame/peak/decoded counters, codec self-test state and verified
resolution count. It deliberately excludes device and installation IDs,
microphone samples, decoded XR payload contents, secrets and Audience events.

Interpret the counters in order:

1. `captured_frames` must increase; zero means the platform is not delivering
   microphone buffers. Build 2 stops iOS capture with the bounded
   `audio_capture_stalled` state when the first frame is still absent after two
   seconds, so the host can offer an explicit retry instead of displaying a
   receiver that appears active forever.
2. `peak_level_milli` must rise above near-silence; a value below 8 after one
   second usually means the signal is too weak or the input route filters it.
3. `decoded_payloads` proves the local acoustic and ggwave path independently
   of network resolution.
4. `verified_resolutions` proves the subsequent signed Core resolution.

Attach this JSON to the physical acceptance record only after reviewing it;
do not add a device identifier or any recorded audio.

Verify the completed file against the exact published archive manifest with:

```bash
node verify-physical-acceptance.mjs /secure/path/evidence.json /secure/path/0.3.28-r3-release.json
```

Download `/secure/path/0.3.28-r3-release.json` from
`https://sdk.xaere.io/releases/0.3.28-r3.json`; do not silently validate old
evidence against a moving `latest.json` alias.

The verifier requires Android and iOS, all four acoustic scenarios, zero replay
increment within eight seconds, fail-closed revocation, the displayed native
codec/capture configuration, application build 2 and the exact preview artifact
SHA-256. Build 1 evidence is rejected for both platforms. The evidence is kept
as a separate mode-`0600` release record and never changes the signed preview
manifest. Stable promotion remains a separate reviewed step that creates and
signs a new stable manifest; it must never rewrite the signed r3 manifest.

### Promote verified r3 evidence to a stable release

Run promotion only on the offline release workstation after the completed
evidence file passes the verifier. Keep the Ed25519 private key outside the VPS
and source tree. The destination directory must not already exist:

```bash
node promote-sdk-release.mjs \
  0.3.28-r3.json \
  xaere-flutter-sdk-0.3.28-preview-r3.tar.gz \
  /secure/path/flutter-physical-acceptance-0.3.28.json \
  /offline/path/xaere-sdk-release-ed25519-private.pem \
  2026-08-15 r1 \
  /secure/path/xaere-sdk-0.3.28-stable-r1
```

The command independently validates both physical platforms and all acoustic
scenarios, checks the exact r3 archive digest and length, and then creates:

- `0.3.28-stable-r1.json` with both physical validation fields set to `true`;
- `0.3.28-stable-r1.signature.json` signed over the exact new manifest bytes;
- `xaere-flutter-sdk-0.3.28-stable-r1.tar.gz`, byte-identical to accepted r3.

Verify the three outputs with `verify-sdk-release.mjs` and the public release
key before copying them into the matching `/releases/` and `/downloads/`
locations. Then create `releases/stable.json` and
`releases/stable.signature.json` as exact byte-for-byte copies of the immutable
stable manifest and signature. Run:

```bash
curl -fsSLO https://sdk.xaere.io/tools/verify-sdk-release.mjs
curl -fsSLO https://sdk.xaere.io/tools/verify-stable-channel.mjs
node verify-stable-channel.mjs /path/to/public/sdk
```

The stable-channel verifier rejects alias drift, an unsafe or moving signature
URL, incomplete physical claims, a mismatched public key and any altered
manifest, signature or archive. Publishing `latest.json` as an exact copy of
the reviewed stable manifest is a final, separate operation.

For the full lifecycle, diagnostics and privacy contract, continue with
`https://docs.xaere.io/FLUTTER_ADAPTER.md`.
