13728285815b
docs: inline notes for tombstone accumulation, per-entry LWW limitation, flags2 format caution; expand Projects.md §3.8 with full permission list and justifications (v3.6.117)
- time
- 2026-05-28 20:17 UTC · 5 months ago
- machine
- koh2
- parent
- 3bc26b9c7542
↓ peach-browser-13728285.tar.gzatlas at this save
files changed6 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/chirp-sync-vault-adapter.ts+7 −0src/background/vault.ts+6 −0src/crypto/peachPayload.ts+7 −0manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22 "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33 "manifest_version": 3,44 "name": "Peach Password Manager",5- "version": "3.6.116",5+ "version": "3.6.117",66 "description": "Zero-knowledge password manager",77 "browser_specific_settings": {88 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "3.6.116",4+ "version": "3.6.117",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "3.6.116",3+ "version": "3.6.117",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/background/chirp-sync-vault-adapter.ts+7 −0
@@ -91,6 +91,13 @@9191 },9292 9393 async buildTombstoneManifest() {94+ // KNOWN LIMITATION: tombstones accumulate without bound. Every deleted95+ // entry leaves a {id, deletedAt} record that persists indefinitely and is96+ // sent in every sync session, so a long-lived vault's manifest grows with97+ // total deletions, not live entries. Acceptable for now (records are tiny98+ // and the trash-retention purge eventually splices the entry array).99+ // Future: prune tombstones older than N months once every paired device100+ // has confirmed it holds no entry with that id.94101 requireUnlocked();95102 const manifest: Record<string, number> = {};96103 for (const entry of getDeletedEntries()) {src/background/vault.ts+6 −0
@@ -1770,6 +1770,12 @@17701770 remoteEntries: VaultEntry[],17711771 remoteTombstones: Record<string, number>,17721772 ): Promise<{ received: number; applied: number; conflicts: number }> {1773+ // DESIGN: last-write-wins is applied PER-ENTRY, not per-field. If two devices1774+ // concurrently edit different fields of the same entry, the one with the1775+ // newer modifiedAt wins the whole entry and the other device's field edits1776+ // are dropped silently. This is the deliberate trade-off for a simple,1777+ // conflict-free merge — per-field CRDT merge is out of scope. Guidance for1778+ // users: edit a given entry on one device at a time.17731779 if (!derivedKey) throw new Error("vault_locked");17741780 17751781 const received = remoteEntries.length + Object.keys(remoteTombstones).length;src/crypto/peachPayload.ts+7 −0
@@ -112,6 +112,13 @@112112 113113 // v3-only flags2 byte (login entries only). Reserves room for further114114 // post-v3.6.82 fields without another envelope bump.115+//116+// CAUTION: flags2 bits are read positionally within FORMAT_V3. Adding a new117+// boolean flag bit here does NOT require a format bump. But adding a new118+// VARIABLE-LENGTH field gated by a new flag bit DOES — older v3 decoders don't119+// know to skip the new field and the read cursor desyncs for everything after120+// it. If you need a new variable-length field, bump FORMAT_V3 → FORMAT_V4 and121+// gate the decoder on the format byte. Only add single-bit booleans to flags2.115122 const F2_HAS_EMAIL = 1 << 0;116123 117124 const NETWORK_VALUES: CardNetwork[] = ["visa", "mastercard", "amex", "discover", "unknown"];