# Private Sync blocker register and remediation plan

**Opened:** 2026-07-16  
**Baseline reviewed:** `af3b6d6`  
**Status:** Remediation in progress; Private Sync is not production-approved.  
**Reviewers:** Fable 5 hostile security and distributed-systems passes, plus Pi and Codex remediation reviews. AI review does not replace the required independent cryptographic/protocol audit or qualified export-compliance review.

This document records every launch blocker surfaced during the Fable 5 review loop, the invariant required to close it, and the regression signal that must remain green. Findings are closed only after implementation, tests, and a later Fable 5 audit agree that the blocker is resolved.

## Blocker register

| ID | Finding | Failure mode | Required invariant | Planned regression signal | Status |
| --- | --- | --- | --- | --- | --- |
| PS-01 | Initializing vault is a one-way freeze | Vault creation freezes legacy writes before phrase confirmation; uninstall/abandonment can leave all clients frozen indefinitely. | An unconfirmed initializing vault expires or can be abandoned without deleting legacy plaintext; a confirmed migration remains resumable. | Convex tests for expiry/abandonment, idempotency, active-vault refusal, and preserved legacy rows. | Closed by Fable 5 round 2 |
| PS-02 | Interrupted cutover cannot resume on a fresh device | Legacy import runs only with no vault; recovery into an initializing vault can never encrypt missing legacy rows. | Every initializing-vault bootstrap imports legacy rows before encrypted sync, regardless of whether setup is new, resumed, linked, or recovered. | Client policy test plus Convex integration timeline covering partial upload, fresh recovery, re-import, and successful cutover. | Closed by Fable 5 round 2 |
| PS-03 | Cutover checks identity but not recency | A stale encrypted row can authorize deletion of a newer plaintext row with the same client ID. | Cutover requires a current-vault encrypted row whose `updatedAt` is greater than or equal to each legacy row. | Convex stale/equal/newer/mixed-version cutover tests. | Closed by Fable 5 round 2 |
| PS-04 | Entitlement expiry destroys recovery and can partially wipe a vault | RevenueCat expiry schedules batched deletion of tasks, devices, vault, and recovery envelope; resubscription can observe divergence after a partial wipe. | Entitlement state controls access only. No automatic expiry job deletes Private Sync records; explicit account deletion is the only destructive cloud path. | Convex tests for active → grace → expired → resubscribed with all records preserved, including invocation of already-scheduled cleanup jobs. | Closed by Fable 5 round 2 |
| PS-05 | One codec-invalid local row blocks the entire outbox | Batch-wide `Promise.all` rejects all rows, loops forever, and prevents cutover. | Rows seal independently; valid siblings continue syncing; a failed row is quarantined for that exact local version and retried only after it changes. Cutover remains fail-closed while a required legacy row is unresolved. | Pure batch-planning test and engine-level valid-plus-invalid fixture. | Closed by Fable 5 round 2 |
| PS-06 | Wall-clock regression can make a task uneditable | A future-skewed timestamp wins for months; later edits get smaller timestamps and are repeatedly reverted. | Every existing-row mutation uses `max(Date.now(), task.updatedAt + 1, syncedAt + 1)` with safe-integer checks. | Unit tests for same-millisecond, backward-clock, future-row, and batch updates. | Closed by Fable 5 round 2 |
| PS-07 | Recovery replacement commit correctness needs stronger proof | Fable questioned replacing a document containing Convex system fields; the current happy-path test did not prove old recovery authorization is invalidated. | Commit uses an explicit patch/replacement payload; after commit only the new recovery capability authorizes recovery and pending fields are gone. | Convex old-proof rejection/new-proof acceptance and candidate-binding tests. | Closed by Fable 5 round 2 |
| PS-08 | Device-link post-wrap verification is non-blocking | A malicious backend can substitute an attacker-known vault key, causing detectable but confusing split-vault denial of service; current words are diagnostic rather than an explicit decision. | Request and connection words are distinct. The requester must confirm post-wrap connection words before persisting the key or starting sync; mismatch discards in-memory key material. | Backend finalization/cancellation tests plus runtime/UI state review. | Closed by Fable 5 round 2 |
| PS-09 | Static capabilities are operational secrets | Vault/device/recovery bearer capabilities cross Convex as arguments and would be replayable if an observability layer logged them. | No app/backend code logs arguments or capabilities; production Convex/proxy behavior is verified and recorded. A challenge/signature protocol remains an independent-audit decision, not an unreviewed rewrite. | Repository secret-log scan plus a release checklist item confirming production argument/log behavior. | Open release gate |
| PS-10 | Session-authorized account deletion can destroy availability | A stolen Clerk session can request account deletion, but locked-out legitimate users must still be able to delete their account. | Threat model explicitly limits stolen-session protection to confidentiality and acknowledges session-authorized deletion/DoS; UI requires destructive confirmation and deletion remains idempotent. | Documentation review and account-deletion integration tests. | Closed as documented threat boundary |
| PS-11 | Full-vault read/cutover ceiling | More than 5,000 rows fails closed; full reactive reads also scale poorly before that point. | No silent truncation. Initial launch has a disclosed fail-closed limit and warning/telemetry plan; pagination or safe compaction lands before any cohort approaches it. | Boundary tests and release monitoring checklist. | Accepted limitation pending monitoring |
| PS-12 | Forward cryptographic revocation is not implemented | A removed device retaining the vault key can still decrypt ciphertext it already has and any future ciphertext obtained outside backend access controls. | UI and claims say access removal only. Forward revocation is claimed only after audited key rotation and complete re-encryption exist. | Copy review and protocol audit. | Accepted limitation |
| PS-13 | Independent security and export reviews remain external gates | Internal tests and AI review cannot approve cryptography, marketing claims, or Apple/U.S. export classification. | Independent protocol/crypto review closes all findings; qualified export review records the correct App Store declaration/filing path. | Signed review records referenced by the private release checklist. | External blocker |
| PS-14 | Final physical-device QA has not run | Metro exports do not validate Keychain/Keystore, camera, biometric, screenshot protection, reinstall, or background behavior. | Current preview build passes the documented iOS and Android smoke matrix on physical hardware before production release. | Manual QA record with build IDs, devices, results, screenshots, and feedback. | Open QA gate |

## Remediation order

### Phase 1 — prevent irreversible lifecycle/data loss

1. Add confirmation-aware initializing-vault expiry/abandonment while preserving legacy rows.
2. Re-import legacy rows whenever the vault is initializing.
3. Make cutover verification recency-aware.
4. Stop all entitlement-triggered destructive cleanup and preserve the recovery anchor.
5. Strengthen recovery replacement commit and recovery-proof tests.

### Phase 2 — guarantee forward progress and convergence

6. Seal dirty rows independently and quarantine only the failing local version.
7. Centralize monotonic task timestamp generation and use it in every existing-row mutation.
8. Ensure a bad backend row cannot prevent valid siblings from reconciling; preserve fail-closed local plaintext behavior.

### Phase 3 — harden linking and policy boundaries

9. Split pre-approval request words from post-wrap connection words and require explicit requester confirmation before activation.
10. Document session-authorized account deletion as an availability boundary while retaining legal deletion access for locked users.
11. Verify production logs never retain capability arguments; leave any signature/challenge redesign to the independent protocol review.
12. Keep revocation wording explicitly access-only.

### Phase 4 — audit and release loop

13. Run all unit, Convex, type, lint, Expo Doctor, and iOS/Android export checks.
14. Run Fable 5 hostile security/distributed-systems review. Add every new blocker to this register and repeat Phases 1–3 until Fable reports no blockers.
15. Run Fable 5 UI/UX review. Fix and repeat until it explicitly says the flow is ready for manual QA.
16. Build an EAS preview artifact from the clean reviewed commit.
17. Execute the physical-device smoke matrix and capture feedback before any production claim or rollout.

## Deliberate policy decisions

- **Entitlement lapse:** pauses cloud access; it does not delete cloud records. Explicit account deletion owns destructive cleanup.
- **Account deletion:** a valid Clerk session may request deletion even when no vault key remains. This means a stolen session can cause denial of service, but cannot decrypt task content. The app must state this boundary rather than falsely promising availability against account compromise.
- **Device removal:** v1 removes backend access only. It does not erase a remote device or rotate the shared vault key.
- **Scale:** the 5,000-row ceiling is fail-closed, never partial. It is acceptable only for an early cohort with monitoring and a committed pagination/compaction milestone.
- **Security status:** audited noble/scure primitives do not make Glide's composition audited. Task-content E2EE marketing remains blocked pending independent review.

## Closure evidence

- Round-1 remediation checks: 39 Bun tests and 14 Convex integration tests passed before the first follow-up audit.
- Fable 5 round 1 passed PS-01 through PS-07 and reopened PS-08 because a backend-provided `finalized` link status could bypass local word confirmation. The fast path was removed; both `approved` and `finalized` now require the same local human decision, with a pure policy regression test.
- Fable 5 round 2 re-audited lifecycle, cutover, entitlement retention, recovery, outbox, LWW, authorization, account isolation, backend finalization, and both requester entry points. Verdict: **CLEAN — NO IMPLEMENTATION BLOCKERS**. It explicitly confirmed that `completeApprovedLink` no longer persists or activates any key and that only the local “Words match” action can finalize and activate.
- Preview deployment preflight on 2026-07-17 found zero `privateSyncVaults` documents in `superb-setter-633`, so no `setupConfirmedAt` backfill is required before this isolated QA rollout.
- RevenueCat Test Store client and V1 server keys are configured separately for preview; values remain outside source control.

For each later closed item, append the fixing commit, test name/command, and Fable audit disposition here. Do not delete historical findings; this register is the review trail.
