Version ledger migrations and hardened JSON portability #61
Open
rockachopa
wants to merge 3 commits from
timmy/35-version-ledger-migrations into main
pull from: timmy/35-version-ledger-migrations
merge into: stackchain:main
stackchain:main
stackchain:timmy/11-mobile-capture-recovery
stackchain:free/hy3-timmy-16
stackchain:timmy/41-release-observability-dashboard
stackchain:free/step-timmy-14
stackchain:timmy/21-escalation-regression-suite
stackchain:timmy/17-bounded-inference-queue
stackchain:timmy/58-strict-csp-runtime-config
stackchain:timmy/55-staging-deployment-tooling
stackchain:timmy/53-private-subpage-staging
stackchain:timmy/10-reproducible-smolvlm-bootstrap
stackchain:timmy/48-vomiting-phrase-hotfix
stackchain:timmy/48-server-safety-override
stackchain:timmy/47-sleek-hermes-agent
stackchain:timmy/9-product-boundary-decisions
stackchain:timmy/8-ci-gates
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 6f73b8551c |
fix: close second hostile-review round on ledger portability
All checks were successful
Quality gates / quality (pull_request) Successful in 4m14s
- collision-safe ID repair: duplicate ids inside stored data are repaired deterministically (first keeps id, twins get id#2, id#3, ... scanning past owned suffixes); every distinct local record survives, never dropped or silently merged; hostile id types (Symbol/BigInt/objects) repair onto fresh deterministic ids instead of throwing - transactional import: parse+merge into a candidate ledger, persist first, then commit memory; quota/error rolls back in-memory state and localStorage together with explicit user feedback; total 16MiB portability budget enforced before mutation on export, import (post-migration expansion), and storage writes - strict Timmy legacy contract for bare top-level arrays: nonempty array of plain rows each carrying a nonempty string id and integer Bristol 1-7; arbitrary unrelated arrays are rejected wholesale - no invented medical defaults from foreign JSON - canonical raster photo validation: strict JPEG/PNG/WebP grammar, canonical base64 (linear scan, no regex on multi-MB strings), atob round-trip decode, declared-format magic bytes, 32B-4MiB decoded bounds; mislabeled SVG/HTML and noncanonical tiny junk are stripped while genuine photos survive byte-for-byte - migrateStoredLedger: localStorage is validated and migrated before render; invalid dates become safe ISO timestamps, duplicate ids repaired, junk rows dropped (never fabricated into default records); healthy storage is byte-stable and never rewritten - sanitizeEntry absorbs Symbol/BigInt/hostile dates/throwing toString, valueOf, getTime, toJSON without throwing; results stay serializable - browser regression suite: quota rollback, pre-render migration, array rejection, photo contract, duplicate-ID preservation in the real app flow - staging-health startup-rejection budget anchored to measured server cold-start instead of a fixed 800ms (fixes load-sensitive flake) |
|||
| dd86d6675d |
fix: close hostile-review blockers in ledger portability
Some checks failed
Quality gates / quality (pull_request) Failing after 1m28s
- provenance origin set is own-safe exact membership (Set.has); inherited toString/constructor/__proto__ names can never become origins - import/export symmetry restored with an explicit bounded policy: MAX_IMPORT_BYTES raised 2 MiB -> 16 MiB UTF-8 bytes, above any export this app can produce (photos capped at 4 MiB binary), so valid exports always re-import without silent data loss while hostile files stay bounded - byte limit is byte-exact now: utf8ByteLength() measures real UTF-8 bytes (multibyte boundaries tested), and the browser rejects oversized files by File.size BEFORE File.text() reads user data - collision-safe deterministic mergeLedgers(): existing user-owned rows win, incoming rows only ever added for new ids, intra-file duplicates collapse deterministically, every collision reported explicitly in the import toast (no duplicate/overwrite/shadow of user records) - base-path Delete Everything is namespace-scoped: root still cleans/ migrates the legacy store to prevent resurrection, /timmy-staging no longer erases another namespace's global legacy ledger (browser regression covers deletion with root legacy data present) - strict current-schema values: Bristol 1-7 / urgency 0-4 / discomfort 0-4 must be true integers (out-of-range falls back instead of silent clamping), photos restricted to JPEG/PNG/WebP base64 raster data URLs (SVG/GIF/non-base64 dropped), invalid dates never throw or persist Invalid Date values Verification: npm test 91/91, test:ui/test:photo/test:sleek/test:portability PASS, staging-deploy 20/20 OK, check:syntax clean, npm audit 0 high, check_diff clean, adversarial probe battery (exact-byte boundary at cap, prototype pollution via JSON, lone surrogates, data-URL strictness) green. |
|||
| b8532f587d |
feat: version ledger migrations and hardened JSON portability
All checks were successful
Quality gates / quality (pull_request) Successful in 1m42s
Implements #35. - importLedger migrates prior schema versions (v0 bare-array legacy exports and the v1 envelope) and fails safely on future versions, malformed JSON, wrong-product envelopes, and oversized files with a new 2 MiB MAX_IMPORT_BYTES guard applied before parsing. - exportLedger normalizes entries through sanitizeEntry so confirmed values and bounded provenance round-trip while smuggled secrets and unknown fields never enter the portable file. - Entries may carry a whitelisted provenance origin ('user' or 'ai-suggestion'); mergeVisualSuggestion records 'ai-suggestion' only when a suggestion is actually applied, keeping nonvisual fields user-owned. - App import now merges into the existing ledger instead of replacing it, so a failed or partial import can never silently drop user-owned records. - Service-worker shell cache bumped to v6 (per base-path namespace) so installed PWAs receive the migration code; old v5 caches are purged on activation. - New tests/ledger-portability.acceptance.mjs browser gate covers export round trip, merge import, safe-failure surfacing, root vs /timmy-staging storage isolation, and Delete Everything for both namespaces; wired into package.json test:portability and CI quality.yml. Deterministic medical safety unchanged: urgent-flag detection, red-flag copy, and chat escalation paths are untouched; all fixtures synthetic. |