learnCN / fluentCN — Chính sách quyền riêng tư
Bản tiếng Việt là bản chính thức. Bản tiếng Anh bên dưới chỉ để tham khảo.
Áp dụng cho ứng dụng iOS/AndroidfluentCN(bundle idapp.learncn.mobile).
Có hiệu lực từ 07/11/2026 (phiên bản 1.0).
Mọi câu trong tài liệu này đều đối chiếu được với mã nguồn; bảng đối chiếu ở
Phụ lục A (tiếng Trung, dành cho nội bộ).
---
1. Tóm tắt trong 30 giây
- Không có tài khoản. Không đăng ký, không email, không số điện thoại, không mật khẩu.
- Bản phát hành trên App Store không gửi bất kỳ dữ liệu nào của bạn. Không sự kiện,
không bản ghi âm, không văn bản. Chỉ có tải tệp nội dung xuống (mục 7).
- Tiến độ học chỉ nằm trên máy bạn (một cơ sở dữ liệu SQLite trong thư mục riêng của app).
- Bản ghi âm chỉ được chấm điểm ngay trên máy, không lưu thành tệp, không bao giờ được tải lên.
- Không có quảng cáo, không có SDK phân tích của bên thứ ba, không có mã theo dõi.
- Bản nội bộ (TestFlight) — bản dùng để thử nghiệm trước khi phát hành, không phải bản trên
App Store — có thêm một kênh số liệu ẩn danh (chỉ loại sự kiện và số đếm). Chi tiết ở mục 5.
---
2. Ai chịu trách nhiệm
Ứng dụng do nhà phát triển độc lập vận hành (sau đây gọi là "chúng tôi"). Liên hệ: TODO(owner) (email hỗ trợ — phải điền trước khi phát hành).
Ứng dụng không có máy chủ nghiệp vụ: không có đăng nhập, không có đồng bộ tài khoản, không có cơ sở dữ liệu người dùng trên đám mây. Hạ tầng duy nhất mà app chạm tới là CDN phân phối âm thanh (mục 7) và — chỉ ở bản nội bộ — máy chủ nhận số liệu ẩn danh (mục 5–6).
---
3. Dữ liệu học của bạn
| Dữ liệu | Nơi lưu | Rời khỏi máy? (bản App Store) |
|---|---|---|
| Tiến độ bài học, bài đang học, cấp độ HSK đã chọn | SQLite trên máy | Không |
| Thẻ ôn tập (SRS), lịch đến hạn, số lần quên | SQLite trên máy | Không |
| Sổ lỗi (âm tiết đọc sai, câu nghe nhầm) | SQLite trên máy | Không |
| Chuỗi ngày học (streak), mục tiêu, thời lượng mỗi ngày, giọng đọc đã chọn | SQLite trên máy | Không |
| Điểm từng lần đọc theo, đường thanh điệu đo được | SQLite trên máy | Không |
Cơ sở dữ liệu nằm trong vùng lưu trữ riêng của ứng dụng và chỉ app này đọc được. Gỡ ứng dụng = xóa toàn bộ dữ liệu học. Chúng tôi không giữ bản sao nào để khôi phục. Bản sao lưu iCloud/Google của thiết bị (nếu bạn bật) do hệ điều hành quản lý, không phải chúng tôi.
Xóa âm thanh đã tải (Cài đặt → Quản lý dung lượng) chỉ xóa tệp âm thanh, không xóa tiến độ, SRS hay sổ lỗi; tải lại là khôi phục.
---
4. Micrô và bản ghi âm
- Micrô chỉ bật khi bạn chủ động nhấn nút ghi âm trong bài luyện đọc theo.
- Âm thanh được đọc thành luồng PCM 16 kHz trong bộ nhớ, chấm điểm bằng mô hình chạy
trên máy, rồi bị bỏ ngay — không ghi ra tệp, không vào thư viện ảnh, không vào bản sao lưu.
- Không có API nào gửi âm thanh ra ngoài, ở bất kỳ bản nào.
- Từ chối quyền micrô: app vẫn dùng được, chỉ bài đọc theo không chấm được điểm.
Câu hệ thống hiển thị đúng như trong Info.plist: «Mic chỉ dùng để chấm điểm phát âm của bạn ngay trên thiết bị. Âm thanh không bao giờ được tải lên.»
---
5. Số liệu sử dụng ẩn danh — chỉ có ở bản nội bộ
Bản phát hành trên App Store không có kênh này. Endpoint nhận số liệu bị bỏ khỏi bản phát hành (app.config.ts, profile release), và app không xếp sự kiện nào vào hàng đợi. Vì vậy trên App Store, App Privacy được khai là không thu thập dữ liệu.
Bản nội bộ (TestFlight, dùng để thử nghiệm trước khi phát hành) có kênh số liệu ẩn danh, mặc định bật, tắt được trong Cài đặt. Mục đích: biết phần nào hỏng, phần nào bị bỏ qua. Không dùng để quảng cáo, không dùng để dựng chân dung người dùng.
Gửi gì (chỉ các trường dưới đây, đã lọc ở phía app):
| Sự kiện | Trường dữ liệu |
|---|---|
session_start / session_end |
dấu thời gian, số thứ tự phiên trong ngày |
plugin_enter / plugin_complete |
mã plugin (nghe / đọc theo / SRS / biến điệu), số giây |
read_submit |
độ dài bản ghi (giây), số âm tiết |
read_verdict |
đạt/không đạt, danh sách mã lỗi thanh điệu, tỉ lệ âm tiết đúng |
listen_answer |
loại câu, đúng/sai, cặp âm đối lập |
srs_review |
số thẻ đã ôn, tỉ lệ "quên" |
backend_profile / app_update_seen |
mức hiệu năng thiết bị, phiên bản |
Không gửi: bản ghi âm, văn bản tự do, tên, email, số điện thoại, ID thiết bị, ID quảng cáo (IDFA), vị trí, danh bạ, ảnh, lịch sử duyệt web.
Phía máy chủ (Cloudflare Worker): sự kiện được lưu theo lô cùng hash SHA-256 của địa chỉ IP có muối xoay theo ngày — chỉ để chặn spam, không lưu IP thô; giới hạn tần suất nằm trong bộ nhớ và tự mất khi worker khởi động lại.
Tắt: Cài đặt → «Gửi thống kê ẩn danh». Tắt là không còn sự kiện nào được ghi vào hàng đợi.
---
6. Hiệu chỉnh giọng đọc — chỉ có ở bản nội bộ, mặc định tắt
- Chỉ bật khi bạn đồng ý trong Cài đặt; mặc định tắt; không xuất hiện ở bản App Store.
- Gửi chỉ số tổng hợp (cao độ đo được theo từng âm tiết, mã lỗi, phiên bản mô hình).
- Không gửi âm thanh, không gửi văn bản.
- Ở bản nội bộ còn có nút xuất thủ công một tệp JSON qua hộp chia sẻ của hệ điều hành —
bạn tự quyết định gửi cho ai.
---
7. Tải âm thanh và mô hình từ CDN
- Âm thanh mẫu và mô hình nhận dạng giọng nói (tùy chọn) được tải từ Cloudflare R2 qua một miền công khai.
- Khi tải, máy chủ CDN thấy những gì mọi máy chủ web thấy: địa chỉ IP, thời điểm, tệp được yêu cầu,
loại thiết bị trong header. Chúng tôi không gắn dữ liệu này với tiến độ học của bạn và không đặt cookie theo dõi.
- Mặc định chỉ tải qua Wi-Fi (đổi được trong Cài đặt). Tệp âm thanh không chứa dữ liệu cá nhân.
- Mô hình nhận dạng (SenseVoice-small, bản INT4, khoảng 123 MB) là tùy chọn:
không tải thì app vẫn chấm điểm, chỉ dùng cách chia âm tiết gần đúng thay vì canh theo mô hình.
---
8. Chia sẻ với bên thứ ba
Chúng tôi không bán, không cho thuê, không trao đổi dữ liệu. Không có bên thứ ba nào nhận dữ liệu học từ app: không có SDK quảng cáo, không có SDK phân tích, không có công cụ báo lỗi từ xa (Sentry hay tương tự).
Nhà cung cấp hạ tầng duy nhất: Cloudflare (R2 cho tệp; Workers chỉ nhận số liệu ẩn danh ở bản nội bộ), chịu ràng buộc bởi chính sách riêng của họ.
---
9. Trẻ em
Ứng dụng hướng tới người học từ 13 tuổi trở lên và không dành cho trẻ dưới 13. Chúng tôi không cố ý thu thập dữ liệu của trẻ dưới 13 tuổi; app cũng không có tài khoản, không có hồ sơ cá nhân, nên không có kênh nào để thu thập.
---
10. Quyền của bạn
Vì dữ liệu học nằm trên máy bạn, bạn kiểm soát trực tiếp:
- Xem: mọi dữ liệu nằm trong app (tiến độ, SRS, sổ lỗi, Cài đặt).
- Xóa một phần: Cài đặt → Quản lý dung lượng (xóa âm thanh/mô hình).
- Xóa toàn bộ: gỡ ứng dụng.
- Không gửi gì: bản App Store vốn không gửi dữ liệu; bản nội bộ có công tắc tắt trong Cài đặt.
- Yêu cầu qua email: nếu bạn tin rằng có dữ liệu về mình đã tới máy chủ của chúng tôi
(chỉ có thể là sự kiện ẩn danh ở bản nội bộ), hãy viết tới địa chỉ ở mục 2; vì không có định danh người dùng, chúng tôi không thể truy ngược một bản ghi về một người cụ thể — và đó chính là lý do thiết kế như vậy.
---
11. Thay đổi chính sách
Khi hành vi của app thay đổi (ví dụ bật một kênh mới trong bản phát hành), tài liệu này được cập nhật trước khi bản phát hành tương ứng lên cửa hàng, kèm ngày hiệu lực mới. Ngày hiệu lực luôn ở đầu tài liệu.
---
English (reference only)
The Vietnamese text above is the binding version.
Summary. No account. The App Store build sends no data at all — no events, no recordings, no text; the only network traffic is downloading content files (§7). Study data (progress, spaced-repetition cards, error book, streak, settings) lives only in a local SQLite database inside the app's own container. The microphone is used only while you press the record button; audio is streamed as in-memory 16 kHz PCM, scored on-device by a local model, then discarded — never written to a file and never uploaded, in any build. There are no ads, no third-party analytics SDKs and no tracking identifiers.
Internal (TestFlight) builds only. The pre-release build used for testing keeps an anonymous usage channel, on by default and switchable off in Settings. It carries enums and counts only: session timestamps and sequence numbers, plugin ids and durations, recording length in seconds, syllable count, pass/fail, tone error-code enums, per-syllable correct ratio, and device-class / app-version tags. No audio, no free text, no name, no email, no device or advertising identifier, no location. The receiving Cloudflare Worker stores the batch with a daily-salted SHA-256 hash of the client IP (anti-abuse only; the raw IP is not stored) and keeps rate-limit counters in memory. The App Store build ships without the endpoint (app.config.ts, profile release), so no event is even queued.
Optional calibration (internal builds only, off by default). Only aggregate pitch metrics, error codes and model version; no audio, no text. An internal-only manual export shares the same JSON through the OS share sheet.
Audio and model downloads. Sample audio and an optional speech-recognition model (SenseVoice-small INT4, ~123 MB) are fetched from Cloudflare R2 over a public host. The CDN sees what any web server sees (IP, time, requested file, user agent). It is not linked to your study progress, and no tracking cookies are set. Downloads default to Wi-Fi only. The model is optional: without it, scoring falls back to an even-split syllable approximation.
Third parties. None beyond Cloudflare (R2 for files; Workers for anonymous event intake in internal builds only). No data is sold. No advertising SDK, no analytics SDK, no remote crash reporter.
Children. 13+; no account and no profile means there is no collection channel for children.
Your controls. Inspect everything in-app; delete downloaded media in Settings without losing progress; delete all data by uninstalling. The App Store build collects nothing to switch off; internal builds expose the switch.
Contact. TODO(owner) — support email, to be filled before release.
---
Phụ lục A — 对源码的依据(内部审计用,不对外)
| 声明 | 代码位置 |
|---|---|
| 无账号、无登录 | app/ 无 auth 路由;.env 的 EXPO_PUBLIC_GATEWAY_* 为空;docs/DEVICE-BUILD-20261107.md |
| 学习数据只存本机 SQLite | @repo/mobile-core LocalDbPort → Documents/SQLite/webfactory.db(表 docs(collection,id,json,updated_at)) |
| 进度/SRS/错题/streak 集合 | 实测 DB collection:course_progress srs_cards tone_read_errors memory_reviews user_profile app_settings |
| 录音内存 PCM、不落盘、不上传 | packages/mobile-core AudioPort(expo-audio,16kHz float32,recordStop → PCM);startFileRecordingAsync 未被调用 |
| 麦克风文案 | apps/mobile/app.config.ts NSMicrophoneUsageDescription |
| Release 版无遥测端点 | apps/mobile/app.config.ts BUILD_PROFILE(默认 release → telemetryEndpoint: null、ingestKey: null);src/platform.ts getTelemetryEndpoint();src/telemetry/use-telemetry.ts endpointUrl() === null → false(不入队) |
| Internal 版遥测默认开、可关 | use-telemetry.ts(enabled !== false)、src/telemetry/dictionary.ts(事件白名单,仅枚举/计数) |
| 隐私清单「不收集数据」成立 | app.config.ts NSPrivacyCollectedDataTypes: [];Release profile 无 endpoint |
| 服务端只存日盐 IP 哈希、内存限流 | calibration-worker/src/index.ts ipHash()(salt = YYYY-MM-DD,SHA-256)、rateLimited() |
| 校准默认关、只发聚合 | src/calibration/queue.ts(loadCalibrationOptIn)、src/calibration/upload.ts(skipped: "opt-out") |
| 反馈自由文本通道已删除 | app/feedback.tsx、src/feedback/ 已删除(2026-11-07);dictionary.ts 的 content_feedback 一并退役;worker 本无 /v1/feedback 路由 |
| 音频从 CDN 下载 | src/download/expo.ts + .env EXPO_PUBLIC_DISTRIBUTION_BASE_URL=https://pub-…r2.dev |
| 模型 ~123 MB、按需下载 | src/download/expo.ts EXPO_PUBLIC_MODEL_PACK_DIR ?? "sensevoice-small-int4";R2 models/sensevoice-small-int4/manifest.json = 2 文件 129,258,743 bytes |
| 仅 Wi-Fi 默认 | src/download/prefetch.ts readDownloadWifiOnly() |
| 无第三方 SDK | package.json 依赖清单:无 Sentry、无 Firebase、无广告 SDK |
Phụ lục B — 发布口径(已定,2026-11-07)
- App Store 版:
LEARCN_BUILD_PROFILE缺省即release→ 无 endpoint、无 key、
不入队、不发请求;App Privacy 标签 = 不收集数据;Settings 内两个网络开关不显示。
- 内部版(TestFlight / 开发):
LEARCN_BUILD_PROFILE=internal→ 遥测默认开(可关)、
校准默认关(可 opt-in),用作发布前唯一真实使用信号。
- 两个 profile 的具体构建命令与验收项见