# 09 · Active config — km range + remote kill-switch (live, 2026-06-28)

> The investigation's headline question, answered across the **whole installed
> cohort** instead of one app: for every Appmiral app that carries the Crowd
> Connected location SDK, **what geofence km range is armed, and is the remote
> kill-switch ON or OFF right now?** This reads each app's **own server-pushed
> config** out of its private on-device storage (root) — we never contact Crowd
> Connected. It extends the single-app
> [`04-live-capture-2026-06-26.md`](04-live-capture-2026-06-26.md) to the live
> fleet.

## Evidence header

```
=== EVIDENCE CAPTURE HEADER (APK harvest) ===
Captured by:     adb over USB, on-device pull
Host wall-clock: 2026-06-28T15:43:11+02:00
Device time:     Sun Jun 28 15:43:12 CEST 2026
Device model:    Pixel 10a (Android 16)
Device build:    google/stallion/stallion:16/CP1A.260505.005/15081906:user/release-keys
ADB serial:      5B161JEA308016
```

Source data (this folder):
[`data/cc_live_config_2026-06-28.csv`](data/cc_live_config_2026-06-28.csv) /
[`.json`](data/cc_live_config_2026-06-28.json). Raw Room DBs + `CrowdConnected.xml`
prefs are **private** (they contain device/account identifiers); only the
**redacted** decoded config is published. Reader:
[`../../tools/apk-harvest/read-cc-config.py`](../../tools/apk-harvest/read-cc-config.py).

## Result — the 6 apps with a populated Crowd Connected config

Of the 140 installed apps, **6** had a live, cached Crowd Connected config at
capture time (an app only has one after it has been launched and the SDK has
polled its backend). Identifiers redacted; **all values are server-pushed**.

| App | Kill-switch | Active km range (`radius2`) | Inner ring (`radius1`) | Geofence centre | Poll / high / med (s) | Schema |
|---|:--:|--:|--:|---|---|---|
| **`com.appmiral.ttfestival`** | **🔴 ON** | **60 km** | 820 m | **52.99, 6.56** (Assen) | 120 / 30 / 1800 | new `Settings.active=1`, SDK 2.1.0, `BACKGROUND` |
| `com.appmiral.mdlbeast` | 🟢 OFF | 60 km (armed) | 282 m | 21.49, 39.19 (Jeddah, SA) | 120 / 30 / 1800 | new `Settings.active=0`, SDK 2.1.0, `BACKGROUND` |
| `com.appmiral.rockforpeople` | 🟢 OFF | 40 km (armed) | 1354 m | 50.25, 15.84 (Hradec Králové, CZ) | 120 / 30 / 1800 | new `Settings.active=0` |
| `com.appmiral.teamnl` | 🟢 OFF | 50 km (armed) | 183 m | 45.45, 9.16 (Milan, IT) | 120 / 30 / 300 | new `Settings.active=0`, SDK 2.1.0, `BACKGROUND` |
| `com.appmiral.museumnachtamsterdam` | 🟢 OFF | 80 km (armed) | 10155 m | 52.36, 4.89 (Amsterdam) | 120 / 30 / 1800 | legacy `activeAPIResponse=120,false,…` |
| `com.appmiral.dutchgp` | 🟢 OFF | — (no zone cached this run) | — | — | 120 / — / — | legacy `activeAPIResponse=120,false,…` |

### What this shows

1. **Only TT Festival was live.** `Settings.active=1` with a **60 km** collection
   ring centred on Assen and the SDK in **background** mode — armed far beyond the
   festival grounds. This corroborates [`04-live-capture`](04-live-capture-2026-06-26.md)
   and the transmission-to-`firehose.us-east-1` evidence there.
2. **The kill-switch is a remote decision.** Every other app shipped an **armed
   geofence** (40–80 km) yet was toggled **OFF** by Crowd Connected's backend at
   capture time. Same SDK, same local `geoEnabled=1`/`sendPositionData=1` flags —
   the only thing that differs is the **server-pushed** switch. The operator can
   flip any of these ON remotely, exactly as TT Festival is.
3. **The geofences are city/region-scale, not venue-scale.** 40–80 km rings cover
   whole metropolitan areas (an 80 km ring on Amsterdam reaches the German border
   side of the Randstad; 60 km on Assen covers most of the north-east Netherlands).

## Where the switch lives (two schemas)

The "on/off" is **not** the local `geoEnabled`/`sendPositionData` flags. It is the
server-pushed activation, stored in one of two places depending on SDK age:

- **New SDK (2.1.0)** — a `Settings.active` **column** in the
  `CrowdConnected-RoomDatabase` Room database. `active=1` → ON, `active=0` → OFF.
  (TT Festival, mdlbeast, rockforpeople, teamnl.)
- **Legacy SDK** — no `active` column; the activation is a heartbeat string
  `activeAPIResponse` in `shared_prefs/CrowdConnected.xml`, formatted
  `<pollSec>,<active>,<…>,<ts>`. `120,false,…` → **OFF**. (museumnachtamsterdam,
  dutchgp.) The decoder reads the prefs file for these apps.

The km range (`GeoZone.radius2`, in metres ÷ 1000 = km) and centre live in the
Room DB for both schemas; `radius1` is the inner high-frequency ring.

## Why this is broader than "12 apps"

The static scan (see [`08-integration-matrix`](08-integration-matrix.md)) shows the
Crowd Connected **capability** — AWS Cognito + Kinesis Firehose + Google Fused
Location — is bundled in **~139/140** apps, even though the literal
`com.crowdconnected` package survives obfuscation in only 9. A given app only
produces a live km/kill-switch reading **after it has been launched** and has
polled config; the 6 above are simply the ones that had been opened. Any of the
other CC-capable apps would populate the same config once launched. So the live
table is a **floor**, not a ceiling, on how many apps can be remotely armed.

## Determinism / caveats (for replication)

- **Server config drifts.** Re-running later may show different switch states or
  different km values — the operator changes them server-side. A non-matching
  switch is **expected** and itself proves remote control.
- **Only launched apps have a DB.** Installed-but-never-opened apps have no cached
  config (recorded as "no runtime config / not launched"). `dutchgp` had a
  heartbeat but an empty `GeoZone` table this session → no km value.
- **Redaction.** `deviceId` is reduced to a salted prefix; alias **values**
  (`app_user_id`, `sns_user_id`) are dropped, keys kept. The `appKey` (a tenant
  key, not personal) is retained for cross-referencing
  [`crowd-connected-endpoints.md`](crowd-connected-endpoints.md).

## Re-verify one app (root, ~1 min)

```powershell
$pkg = "com.appmiral.ttfestival"
adb shell su -c "cp /data/data/$pkg/databases/CrowdConnected-RoomDatabase /sdcard/cc.db"
adb pull /sdcard/cc.db
python -c "import sqlite3;c=sqlite3.connect('cc.db');print('Settings',[r for r in c.execute('select active,geoEnabled,sendPositionData from Settings')]);print('GeoZone',[r for r in c.execute('select lat,lng,radius1,radius2 from GeoZone')])"
# legacy apps (no `active` column) — read the heartbeat instead:
adb shell su -c "cat /data/data/com.appmiral.museumnachtamsterdam/shared_prefs/CrowdConnected.xml" | Select-String activeAPIResponse
```

Full automated reader + reproduction:
[`10-apk-harvest-reproduction.md`](10-apk-harvest-reproduction.md).
