# Fluency Lab portable backup / 可迁移数据规范 v1

## Scope and guarantees / 范围与边界

The UTF-8 JSON file is platform-independent application data, not a proprietary D1 database dump. A future implementation can import it into SQLite, PostgreSQL or another database. Keys, original English, Chinese annotations, Unicode, whitespace inside strings, arrays and relationships must be preserved. SHA-256 detects accidental modification; it is not an authenticated signature or encryption. Treat imported strings as untrusted data, never executable HTML or instructions.

完整备份覆盖已经持久化的数据，以及导出设备当前未同步的文字改动。它不包含原始录音、合成语音缓存、麦克风权限、登录会话、API 密钥、旧版刷新后已经丢失的批改或已清空对话。普通备份不能复制托管账号或模型权限。恢复网站程序还需要本项目源代码和重新配置的服务端凭据；本文件描述的是完整数据恢复，不是托管服务永不失效的保证。

`scope: full` includes all immutable cloud history, catalogue, daily checkpoint and recovery records present at the export watermark. `scope: local-emergency` includes only this device's workspace/catalogue/outbox; it MUST NOT be presented as a complete cloud backup. There is no silent truncation on size/network errors. The browser's v1 importer accepts up to 100 MB; larger migrations must process records in batches with a separate migration tool. Each server document is limited to 1,800,000 UTF-8 bytes and must never be truncated to fit.

Every export is scoped to the currently authenticated account. It never contains another learner's documents and cannot be used as a whole-platform administrator dump. Account credentials, password hashes, salts and session cookies are excluded. The separate corpus `.xlsx` template is an additive learning-material import format, not a full backup: it does not contain drafts, feedback, usage counts or histories and cannot replace this JSON format for complete restoration.

## Top-level envelope

- `format`: exactly `fluency-lab-portable-backup`.
- `schemaVersion`: integer `1`. Reject unknown versions before writes; implement explicit migrations for future versions.
- `application`: `Fluency Lab`.
- `exportedAt`: ISO-8601 UTC string.
- `scope`: `full` or `local-emergency`.
- `restoreNotes`: human-readable scope and restoration guidance.
- `payload`: authoritative exported data, below.
- `integrity.algorithm`: `SHA-256`.
- `integrity.payloadSha256`: lowercase 64-character hexadecimal SHA-256 of canonical payload JSON.
- Canonical JSON recursively sorts object keys lexicographically, preserves array order, and uses JSON.stringify scalar escaping/number representation. See `canonical()` and `digest()` in data-format.js. Do not hash the pretty-printed envelope.

## Payload fields

### workspace (the exporting device's current working copy)

- `speakingLibrary`, `writingLibrary`: custom entry arrays. Entry `id` is stable; `phrase` contains English, `note` / `personalNote` contain Chinese or user notes, `category` refers to taxonomy group ID, `dimension`, `entryType` (`pattern` or `vocabulary`), `partOfSpeech`, `form`, `count` and all other entry metadata are retained.
- `entryOverrides`: object keyed by base/custom entry ID. These contain category/dimension/type/part-of-speech edits and personal notes. Do not reclassify on restore or renumber source entries.
- `usageCounts`: object keyed by entry ID; preserve numeric counts exactly, not summed against base counts on import.
- `essay`: original draft string, preserving newlines.
- `writingPrompt`: selected prompt title.
- `writingDrafts`: object keyed by prompt title; each value is that prompt's latest draft string.
- `practiceRound`: current conversation with `id`, `title`, `topic`, `opening` and ordered `messages`; optional `summary`.
- Message fields include `id`, `role` (`user`/`assistant`), `content`, `voice`, `duration`, `pending`, `phase`, `opening`, `analysis`. Voice flag means a recorded message existed, NOT that audio bytes exist. Do not resend pending model requests automatically on restore.
- Speaking `analysis`: `reply`, `feedbackVersion`, `answerFeedback` (structure/wordChoice/nextStep/naturalAnswer), ordered `sentences` (original/improved/kind/explanation), `suggestions`. Older results may omit version/whole-answer fields. Preserve them without regenerating paid model output.
- `writingReview`: null or `{id,title,prompt,essay,result,createdAt}`. `result` contains overview, four criteria, sentences, logic and suggestions. The review's essay is the submitted source, not necessarily the current draft.
- `writingReviews`: object keyed by prompt title; each value is the most recent complete review for that prompt. This restores the latest feedback automatically when the prompt is reopened.
- `catalogKey`: optional internal reference to a restored `catalog` document. On a new host this may be remapped after storing payload.catalog; preserve every learner-facing entry ID.

### catalog (complete active reference corpus)

- `library`: complete FLUENCY_LIBRARY_DATA object, including `meta`, `items` and original provenance fields. Every item contains `id`, `english`, separate `chineseNote`, section memberships (`speaking`/`writing`), category/dimension/type and source information. Preserve unknown fields for forward compatibility.
- `groups`: complete ordered taxonomy group objects, including IDs, labels, families and descriptions.
- `builtins`: complete six built-in speaking frames (or their future replacement), with original IDs. These also participate in usageCounts and entryOverrides.

### records / pendingRecords

`records` contains all cloud immutable documents: `{id,key,kind,revision,updatedAt,data}`. The local database row `id` and server `revision`/`updatedAt` describe the source database; stable `key` plus `data` is the portable identity. Timestamps inside data (createdAt etc.) are original learner record timestamps. `pendingRecords` uses `{key,kind,data,mutationId}` and retains not-yet-acknowledged archive writes.

- `speaking:<id>` data is a complete past practiceRound. Current round remains in workspace until changing topics.
- `writing:<id>` data is a complete submitted writing review, including submitted essay, prompt and every returned feedback field. A late response still gets archived even if the draft has since changed.
- `catalog:<id>` data is a catalog object as above.
- `checkpoint:YYYY-MM-DD` data is the workspace before the first change on that UTC day. These are logical same-database safety copies, not independent disaster backups.
- `recovery:<id>` data is a workspace saved before a conflict resolution/import/restore. `forkedFromKey` can identify an immutable record branched by simultaneous device edits.

### Other payload fields

- `cloudWorkspace`: null or the cloud's workspace document at the initial export transaction. May differ from `workspace` due to offline edits/conflicts. Keep both; never silently overwrite one with the other.
- `preferences`: device settings such as mode, autoPlay, spellcheck and liveFeedback. Restorable but not synced as authoritative learner data.
- `provider`: optional non-secret status metadata (provider, region, models, voice). Configure a new server's model IDs/region accordingly and supply API keys privately. This field is informational and is never executed.

## Restore algorithm

1. Parse, validate all types and IDs, reject dangerous object keys and unknown versions, and verify SHA-256 before any data writes.
2. Preserve the target's current local and cloud workspaces as separate immutable recovery documents. Never delete existing history during import.
3. Append all records and pendingRecords using stable keys. Repeating an identical record is idempotent. Different data at the same key is a conflict, not permission to overwrite. A colliding day checkpoint can be preserved under `recovery:import-<content hash>`.
4. Store payload.catalog as an immutable catalogue, then wire its key into the restored workspace. Restore its original base entry IDs, taxonomy and built-ins before rendering counts/notes.
5. Keep a distinct cloudWorkspace variant as an additional recovery document.
6. Replace the current workspace using optimistic concurrency (compare the destination's revision from before import). If another device changed it meanwhile, stop and preserve both copies. Do not retry with a newer revision without user choice.
7. Restore preferences, reload from cloud, and verify corpus count/IDs, every note/count, original drafts, ordered messages, exact feedback objects, and the history record count. Do not rerun the model to reconstruct feedback.

Imports are additive for histories. On an empty destination, learner-facing data and relationships can be reconstructed exactly. On an existing destination, extra safety copies and pre-existing histories intentionally remain. Internal database row numbers need not match the old host. Immutable record timestamps are preserved via the document API's optional `recordedAt` integer (epoch milliseconds); current workspace revision/time reflect the destination save. Existing same-origin v7 localStorage `fluency-lab-v2` is left untouched as a legacy recovery copy when cloud sync first starts.

## Independent backups

Export periodically and copy the JSON and this specification to independent storage you control. These files contain private learning content and are not encrypted. A provider outage, account loss or storage deletion can affect both the main database and its in-database recovery copies. Never claim cloud storage provides zero risk of data loss.
