Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

adoption-and-telemetry.md

v0.1.0-build.85 Latest Commit ede38f5

Size
6.7 KB
Lines
136
Language
markdown
SHA-256
a36e8b0cfbd3…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1# Adoption and telemetry
2
3This document is normative for every CKey SDK and for the openckey.org ingestion service. It implements specification §9.
4
5## Modes
6
7| Value | Name | Counts locally | Sends to openckey.org |
8|---|---|---|---|
9| `0` | `CKEY_TELEMETRY_OFF` | no | no |
10| `1` | `CKEY_TELEMETRY_ON` | yes | yes, aggregate batches (**default**) |
11| `2` | `CKEY_TELEMETRY_LOCAL` | yes | never |
12
13Every SDK uses these numbers. Each language may also offer named constants. Setting any other value is a configuration error: `CKEY_INVALID_ARGUMENT` in C, and the language's own error elsewhere. The mode is never silently reset to 1.
14
15Opting out takes one line, for example `CKey.telemetry = 0` (Swift, Kotlin, JS) or `ckey_telemetry_set_mode(0)` (C).
16
17### Switching modes
18
19| From → to | Effect |
20|---|---|
21| any → `0` | Stops counting. **Purges** all counters, including unsent ones. Nothing more is sent. |
22| `1` → `2` | Keeps local totals. **Drops** the unsent delta, so it can never be sent later. If a batch is in flight, its result is ignored. |
23| `2` → `1` | Starts a new unsent delta at zero. Counts made while in mode 2 are never sent. |
24| `0` → `1` or `2` | Starts counting from zero. |
25
26Persisting counters across app restarts is a separate host option (`ckey_telemetry_export_state` / `ckey_telemetry_import_state`). It is off unless the host stores the exported state. Importing never re-enables sending, and the mode is not part of the state.
27
28## What a scan attempt is
29
30In continuous camera mode, the unit is a **scan session**, not a frame:
31
32- **Starts:** when the host starts scanning (`ckey_telemetry_session_begin`), for example when the camera view appears.
33- **Succeeds:** at the first valid decode (`ckey_telemetry_session_success`). Later frames in the same session are not counted.
34- **Fails:** when the host stops scanning without a decode (`ckey_telemetry_session_end`), or when 30 seconds pass with no decode. On timeout, the next frame implicitly starts a new session.
35- **Frames:** processed frames are counted separately (`ckey_telemetry_frame`), together with their processing time.
36
37## Collected fields
38
39Only counts and coarse durations are collected:
40
41- session counts (started, succeeded, failed);
42- the number of frames processed;
43- the number of decodes by profile;
44- histograms of time to first decode and of per-frame processing time;
45- the SDK name and version;
46- a coarse platform name;
47- the UTC day the batch was cut.
48
49The SDK **never** collects:
50
51- payloads, URLs or any decoded content;
52- user, device or installation IDs;
53- advertising IDs or fingerprints;
54- location;
55- IP addresses (beyond what the network itself exposes);
56- per-scan event histories.
57
58Telemetry never runs on the decode path. Recording is a few atomic counter updates. Sending is asynchronous, batched, and never blocks or delays scanning. A failed send keeps the delta for the next attempt, and an offline device simply accumulates counts.
59
60### Send cadence
61
62At most one batch per hour per app process, and only when there is something to send. The SDK also tries a send when the app moves to the background. One request carries one batch, so there is never one request per scan.
63
64## Batch schema, version 1
65
66`POST https://openckey.org/api/telemetry/v1/batch` with `Content-Type: application/json` and a body of at most 4,096 bytes:
67
68```json
69{
70 "schema": 1,
71 "sdk": "swift",
72 "sdk_version": "0.1.0-build.61",
73 "platform": "ios",
74 "day": "2026-11-05",
75 "sessions": { "started": 12, "succeeded": 10, "failed": 2 },
76 "frames": 3120,
77 "decoded": { "p0": 7, "p0_1": 1, "p1": 1, "p2": 1, "p3": 0 },
78 "time_to_decode_ms": { "count": 10, "sum": 4210, "buckets": [0, 1, 3, 4, 1, 1, 0, 0, 0, 0] },
79 "frame_ms": { "count": 3120, "sum": 40110, "buckets": [10, 400, 2000, 600, 100, 10, 0, 0, 0, 0] }
80}
81```
82
83Field rules (the service rejects anything else with `400`):
84
85| Field | Rule |
86|---|---|
87| `schema` | exactly `1` |
88| `sdk` | one of `c`, `swift`, `android`, `web`, `dotnet` |
89| `sdk_version` | matches `^0\.[0-9]+\.[0-9]+(-(build\|dev)\.[0-9]{1,6})?$` |
90| `platform` | one of `ios`, `macos`, `android`, `web`, `windows`, `linux`, `other` |
91| `day` | `YYYY-MM-DD`, at most 2 days in the future and at most 35 days in the past (server clock) |
92| `sessions` | object with exactly `started`, `succeeded` and `failed` |
93| `frames` | integer |
94| `decoded` | object with exactly `p0`, `p0_1`, `p1`, `p2` and `p3` |
95| `time_to_decode_ms` and `frame_ms` | object with exactly `count`, `sum` and `buckets` (10 integers) |
96
97- Every count is an integer from 0 to 10,000,000. A `sum` is an integer from 0 to 10<sup>12</sup>.
98- Bucket counts must add up to `count`.
99- Unknown fields, at any depth, are rejected.
100
101Histogram bucket upper edges, in ms (the last bucket is everything above):
102
103| Histogram | Edges |
104|---|---|
105| `time_to_decode_ms` | 25, 50, 100, 200, 400, 800, 1600, 3200, 6400, ∞ |
106| `frame_ms` | 2, 5, 10, 20, 40, 80, 160, 320, 640, ∞ |
107
108The service replies `204` when it accepts the batch, `400` for an invalid one, `413` if the body is too large, and `429` when rate limited. SDKs drop a batch after a `400` or `413`, and keep it after a `429`, `5xx` or network error.
109
110## Service obligations
111
112- **Storage:** aggregates only, summed per (day, sdk, sdk_version, platform). Raw request bodies are not stored.
113- **IP addresses:**
114 - not written to application logs or the database;
115 - rate limiting uses an in-memory key derived from the IP with a random salt that rotates daily;
116 - nothing derived from the IP is persisted.
117- **Retention:** daily aggregates are kept for 400 days.
118- **Abuse limits:** per-key rate limits, a per-day cap on accepted batches, and size limits. Counts can still be inflated by a determined sender; see below.
119- **Reporting:** the public summary (`GET /api/telemetry/v1/summary`) labels its figures *reported by SDKs that left telemetry on*. These are not total usage, unique users or worldwide scans.
120
121## Integrity caveat
122
123Anonymous counters from open-source SDKs are unauthenticated. They can be spoofed, replayed or inflated, and without identifiers they cannot be deduplicated. Treat the figures as a coarse signal, and publish the methodology alongside them.
124
125## Other adoption evidence
126
127These are reported separately and never merged into one "users" number:
128
129- package downloads (not the same as active installs);
130- GitHub activity;
131- voluntarily registered apps and businesses;
132- opt-in redirect counts from openckey.org short links.
133
134## Privacy disclosures
135
136Integrators must disclose telemetry according to their own app-store and legal requirements. For example, Apple's privacy manifest lists "Product Interaction → Analytics, not linked to the user, not used for tracking" while mode 1 is on. Builds for OS vendors can leave the telemetry module out entirely: the scanner library does not depend on it.