Skip to content

fix: content-stable review ids so resolved state survives ingest regeneration#495

Merged
nashsu merged 1 commit into
nashsu:mainfrom
AndrewDongminYoo:fix/stable-review-ids
Jun 28, 2026
Merged

fix: content-stable review ids so resolved state survives ingest regeneration#495
nashsu merged 1 commit into
nashsu:mainfrom
AndrewDongminYoo:fix/stable-review-ids

Conversation

@AndrewDongminYoo

Copy link
Copy Markdown
Contributor

Problem

Review ids are a module-level incrementing counter (review-N). Any time review generation re-runs — e.g. the ingest queue rebuilds — every review is re-numbered, and because addItems only deduped against pending items, a previously resolved review comes back as a brand-new pending one. Its resolution is silently lost.

It also makes id-based targeting unreliable: a caller (the Review API, or anything holding an id) points at a number that no longer means the same review after a rebuild.

Fix

Make the id content-derived and stable.

  • reviewIdFor(item) = FNV-1a(type + normalizeReviewTitle(title)) — the same logical review gets the same id across regeneration, file moves, and reloads. sourcePath is deliberately excluded (it's mutable; including it would re-id a review on a file rename — the same instability being removed).
  • addItems dedups on the stable id against all items including resolved ones (resolved wins, array fields merged). This is the actual fix for resolved state being dropped when a review re-surfaces.
  • setItems migrates on load: remaps old review-N ids to stable ids and collapses duplicates (resolved wins, union affectedPages/searchQueries, earliest createdAt). Idempotent — the id is computed from content, no migration-version flag — so an existing review.json heals on first load + auto-save.
  • addItem dedups by identity (no revival of a resolved item).

normalizeReviewTitle was already the dedup key; it's now also load-bearing for id identity (noted at reviewIdFor).

Tests

Unit + property tests rewritten for stable-id behaviour, including acceptance cases:

  • resolve a review, then re-add the same content (simulating ingest re-surfacing it) → same id, stays resolved;
  • two counter-id rows with identical content collapse to one resolved item on load.

Context

This is the store-layer counterpart that makes id-based review resolution (e.g. a resolve endpoint) actually reliable — without stable ids, resolving "review-12" is meaningless after the next rebuild.

Review ids were a module-level incrementing counter (review-N). Any
queue rebuild re-ran review generation, which re-numbered every review
and — because addItems only deduped against *pending* items — discarded
resolved state: a resolved review came back as a brand-new pending one.
This also makes id-based targeting (e.g. resolving a review by id from
the API) unreliable, since the same number stops meaning the same thing.

Make the id content-derived and stable:

- reviewIdFor(item) = FNV-1a(type + normalizeReviewTitle(title)). Same
  logical review → same id across regeneration, file moves, and reloads.
  Deliberately excludes the mutable sourcePath.
- addItems dedups on the stable id against ALL items including resolved
  (resolved wins, array fields merged) — the actual fix for resolved
  state being dropped on re-surface.
- setItems migrates on load: remaps old counter ids to stable ids and
  collapses duplicates (resolved wins, union arrays, earliest createdAt).
  Idempotent (id from content, no version flag), so existing on-disk
  review.json heals on first load and re-save.
- addItem dedups by identity (no revival of a resolved item).

normalizeReviewTitle (already the dedup key) is now also load-bearing
for id identity. Tests rewritten for stable-id behaviour incl. property
tests; acceptance cases: resolve-then-reingest keeps id+resolved, and
two counter-id rows with identical content collapse to one resolved item.
@nashsu nashsu merged commit a17e69a into nashsu:main Jun 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants