vault_entries uses SQLite's space-separated UTC format everywhere, and both
sorting and sync last-write-wins compare the strings lexically — so a foreign
JSON import carrying strict-ISO 'T'/millis/Z/offset timestamps would slot in
with a different format and subtly break ordering and merge arbitration.
normalizeImportTimestamp converts any ISO-ish variant to 'YYYY-MM-DD
HH:MM:SS' UTC (bare strings treated as UTC, garbage -> '' = server stamps
now). +1 unit test (69 total).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two CODE_AUDIT items in one session.
§3.2 — Frontend regression net (js/tests/, 35 tests, node:test, zero deps):
- harness.js loads app.js (monofile, no exports) into a node:vm with browser
globals stubbed, surfacing internals via an export epilogue.
- crypto: deriveKeyAndVerifier (AES key == raw PBKDF2, cross-checked vs Node
pbkdf2Sync), legacy-vs-v2 verifier decoupling, encrypt/decrypt round-trip,
IV uniqueness, AEAD tamper/wrong-key.
- csv: parseCSV tokenizer, findColumn heuristics, Bitwarden/KeePass mapping.
- merge: applyRemoteSnapshot add/update/skip (LWW), tombstone delete,
resurrection arbitration (both NaN branches), local-tombstone veto,
additive folder merge. Only api() is stubbed; loadEntries/encryptImportEntry
run for real.
- Wired as a build gate in BuildAssets.ps1 (after node --check, bypass
PM_SKIP_TESTS=1).
§2.2 — Unify timestamps on UTC:
- Entry created_at/updated_at were written via Delphi FormatDateTime(Now)
= LOCAL, while deleted_at/tombstones use SQLite CURRENT_TIMESTAMP = UTC.
The tombstone-resurrection arbitration compared the two zones, skewing by
the machine's UTC offset even single-device.
- Add NowUTC/NowUTCStr to PM.Database, swap in at every entry/attachment
write site (Entries create/update/bulk, Attachments POST echo).
- No JS change needed: arbitration now compares same-zone values.
- Existing rows self-heal on next edit (no destructive migration).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>