You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
While preparing for the Data Enrichment milestone's known-conflict work (#147, kept as-is — not
touched by this issue), verified (2026-07-15) that both bundled files should move from the silent skip default to review, so a genuine conflict is staged for a decision instead of one side being
silently discarded. Counted directly against the bundled files: vilaboim_movie-quotes.json has 0 internal duplicate-id collisions; NikhilNamal17_popular-movie-quotes.json has exactly 9,
matching #147's own documented table precisely.
Rather than hand-deciding those 9 conflicts once via #149's existing decide/undo/apply endpoints
(which wouldn't persist — a future re-seed or Quotinator__AutoUpdateSources refresh of the same
source could reintroduce the identical conflict, requiring the same decision again), the goal is to
resolve conflicts as close to the source as possible: a small, per-source declarative rule file
that staging consults before ever creating a Pending action, auto-resolving a matching, already-
known conflict the same way every time.
This is deliberately a minimal, hand-authored slice of #153's eventual design ("Declarative
conflict-resolution file for recurring third-party source conflicts"). #153's own step 6 (rule
lookup and auto-apply during staging) is exactly what this issue builds — hand-authored rather than
generated. #153's remaining scope (generating rules from a batch's decided actions, staleness
detection, a rule-file GET/generate-merge endpoint) stays with #153, still gated on #163 landing
first for its generation step. This issue's rule-file format is what #153 builds on top of later,
not a format #153 will replace — #153's plan doc is updated to reflect this in the same commit.
Separately, a new curated field-override preload file
(data/sources/quotinator-source-overrides.json) sets the authoritative value for specific fields
on rows that originate in an external bundled source but need a correction (e.g. the correct date
for one of the 9 disputed NikhilNamal17 quotes) — distinct in purpose from quotinator-curated.json
(which adds wholly new, fully-curated quotes, not overrides on rows seeded from elsewhere). This
preload is what a source's own rule file resolves against: the rule says "for quote X's date
field, keep the existing (curated-preloaded) value over whatever this source's incoming re-import
says."
Scope widened 2026-07-25 by #217 (new parent tracking issue, alongside #153 and #177): from the
originally-named 2 external bundled files to all 4 currently-bundled files — quotinator-curated.json and quotinator-series-universe.json (internally-authored) in addition to vilaboim_movie-quotes.json and NikhilNamal17_popular-movie-quotes.json. We cannot predict how a
new bundled file will interact with data already seeded from another one, and forcing every bundled
file through review policy with its own conflict-resolution file is what makes that interaction
tractable — limiting this to only the two files that happen to already have known external conflicts
would leave the internally-authored files unverified against the same standard. This issue is now
sequenced under #217 as #177 → #181 → #153 — #177's ImportBatches.Status fix lands first because #217's own resolve→apply→reverse→retry testing methodology needs a working POST /import/actions/reverse.
Wire rule lookup into ImportActionPlanner.PlanAsync's conflict-staging logic — if a matching rule
exists for a field, the action resolves automatically instead of staging Pending — mirrors Declarative conflict-resolution file for recurring third-party source conflicts #153's
own step 6 exactly, scoped down (no generation, no staleness detection, no endpoint; these rule
files are static and hand-edited, not machine-generated). Note: the codebase has grown since
this item was first written — there is no longer one single "conflict-staging branch"; each
entity-specific Plan*Async method has its own isPending check. The originally-known conflicts
(NikhilNamal17's 9) are Quote-level, so the top-level PlanAsync Quote logic is the minimum needed
for those — confirm whether the widened 4-file scope (item 4) surfaces any Series/Universe-level
conflicts from quotinator-series-universe.json that would need PlanSeriesAsync/ PlanUniverseAsync wired too, rather than assuming Quote-only.
Set duplicateResolution: review for all 4 currently-bundled files in data/sources/manifest.json — quotinator-curated.json, quotinator-series-universe.json, vilaboim_movie-quotes.json, and NikhilNamal17_popular-movie-quotes.json (widened from the
original 2-file scope, see Background).
One rule file per bundled file, all 4 — a rule file for NikhilNamal17 with 9 entries (one per
known conflict), each resolving to the value the override-preload file establishes, and
initially-empty rule files for vilaboim, quotinator-curated, and quotinator-series-universe
(widened from the original 2-file scope), so the manifest reference and lookup path are exercised
consistently for every bundled file and each stays independently updatable.
Confirm live: reseeding with review set for all 4 files produces zero staged Pending
actions — all 9 NikhilNamal17 conflicts auto-resolve via the rule file; the other three have none
to begin with (widened from the original 2-file scope).
These per-source rule files double as smoke-test fixtures: the T2 checklist (CLAUDE.md's
living smoke-test list) exercises multiple scenarios directly against them — a matching rule
auto-resolving without a Pending action, a field with no matching rule still staging Pending
as before, and (by temporarily editing a rule file's value) confirming a changed rule changes the
auto-resolved outcome on the next reseed. Add this to CLAUDE.md's T2 checklist in the same commit
that ships the mechanism, per this project's "living checklist, only grows" convention.
SeedVilaboim_ReviewPolicy_NoStagedActions, plus equivalents for quotinator-curated and quotinator-series-universe (widened from vilaboim-only)
❌
The first two tests are the same ones #153 lists in its own Expected tests table — implementing this
issue ships the mechanism they exercise, so #153 inherits them as already-passing regression guards
rather than writing them fresh when #153 itself lands.
Definition of done
All expected tests listed above start red before implementation
Background
While preparing for the Data Enrichment milestone's known-conflict work (#147, kept as-is — not
touched by this issue), verified (2026-07-15) that both bundled files should move from the silent
skipdefault toreview, so a genuine conflict is staged for a decision instead of one side beingsilently discarded. Counted directly against the bundled files:
vilaboim_movie-quotes.jsonhas0 internal duplicate-id collisions;
NikhilNamal17_popular-movie-quotes.jsonhas exactly 9,matching #147's own documented table precisely.
Rather than hand-deciding those 9 conflicts once via #149's existing decide/undo/apply endpoints
(which wouldn't persist — a future re-seed or
Quotinator__AutoUpdateSourcesrefresh of the samesource could reintroduce the identical conflict, requiring the same decision again), the goal is to
resolve conflicts as close to the source as possible: a small, per-source declarative rule file
that staging consults before ever creating a
Pendingaction, auto-resolving a matching, already-known conflict the same way every time.
This is deliberately a minimal, hand-authored slice of #153's eventual design ("Declarative
conflict-resolution file for recurring third-party source conflicts"). #153's own step 6 (rule
lookup and auto-apply during staging) is exactly what this issue builds — hand-authored rather than
generated. #153's remaining scope (generating rules from a batch's decided actions, staleness
detection, a rule-file GET/generate-merge endpoint) stays with #153, still gated on #163 landing
first for its generation step. This issue's rule-file format is what #153 builds on top of later,
not a format #153 will replace — #153's plan doc is updated to reflect this in the same commit.
Separately, a new curated field-override preload file
(
data/sources/quotinator-source-overrides.json) sets the authoritative value for specific fieldson rows that originate in an external bundled source but need a correction (e.g. the correct
datefor one of the 9 disputed NikhilNamal17 quotes) — distinct in purpose from
quotinator-curated.json(which adds wholly new, fully-curated quotes, not overrides on rows seeded from elsewhere). This
preload is what a source's own rule file resolves against: the rule says "for quote X's
datefield, keep the existing (curated-preloaded) value over whatever this source's incoming re-import
says."
Scope widened 2026-07-25 by #217 (new parent tracking issue, alongside #153 and #177): from the
originally-named 2 external bundled files to all 4 currently-bundled files —
quotinator-curated.jsonandquotinator-series-universe.json(internally-authored) in addition tovilaboim_movie-quotes.jsonandNikhilNamal17_popular-movie-quotes.json. We cannot predict how anew bundled file will interact with data already seeded from another one, and forcing every bundled
file through
reviewpolicy with its own conflict-resolution file is what makes that interactiontractable — limiting this to only the two files that happen to already have known external conflicts
would leave the internally-authored files unverified against the same standard. This issue is now
sequenced under #217 as
#177 → #181 → #153— #177'sImportBatches.Statusfix lands first because#217's own resolve→apply→reverse→retry testing methodology needs a working
POST /import/actions/reverse.What needs to be done
discussion in its plan doc — this issue's fully-known, small conflict set makes the alternative
content-hash keying scheme unnecessary here; do not re-decide this independently of Declarative conflict-resolution file for recurring third-party source conflicts #153's own
reasoning).
matching Declarative conflict-resolution file for recurring third-party source conflicts #153's own item 2 design.
ImportActionPlanner.PlanAsync's conflict-staging logic — if a matching ruleexists for a field, the action resolves automatically instead of staging
Pending— mirrors Declarative conflict-resolution file for recurring third-party source conflicts #153'sown step 6 exactly, scoped down (no generation, no staleness detection, no endpoint; these rule
files are static and hand-edited, not machine-generated). Note: the codebase has grown since
this item was first written — there is no longer one single "conflict-staging branch"; each
entity-specific
Plan*Asyncmethod has its ownisPendingcheck. The originally-known conflicts(NikhilNamal17's 9) are Quote-level, so the top-level
PlanAsyncQuote logic is the minimum neededfor those — confirm whether the widened 4-file scope (item 4) surfaces any Series/Universe-level
conflicts from
quotinator-series-universe.jsonthat would needPlanSeriesAsync/PlanUniverseAsyncwired too, rather than assuming Quote-only.duplicateResolution: reviewfor all 4 currently-bundled files indata/sources/manifest.json—quotinator-curated.json,quotinator-series-universe.json,vilaboim_movie-quotes.json, andNikhilNamal17_popular-movie-quotes.json(widened from theoriginal 2-file scope, see Background).
data/sources/quotinator-source-overrides.jsonwith the correct values for NikhilNamal17's9 known conflicts, sourced from NikhilNamal17 bundled source: 9 same-quote/source rows collide on differing year metadata #147's own findings table.
NikhilNamal17with 9 entries (one perknown conflict), each resolving to the value the override-preload file establishes, and
initially-empty rule files for
vilaboim,quotinator-curated, andquotinator-series-universe(widened from the original 2-file scope), so the manifest reference and lookup path are exercised
consistently for every bundled file and each stays independently updatable.
reviewset for all 4 files produces zero stagedPendingactions — all 9 NikhilNamal17 conflicts auto-resolve via the rule file; the other three have none
to begin with (widened from the original 2-file scope).
living smoke-test list) exercises multiple scenarios directly against them — a matching rule
auto-resolving without a
Pendingaction, a field with no matching rule still stagingPendingas before, and (by temporarily editing a rule file's value) confirming a changed rule changes the
auto-resolved outcome on the next reseed. Add this to CLAUDE.md's T2 checklist in the same commit
that ships the mechanism, per this project's "living checklist, only grows" convention.
153-declarative-conflict-resolution-plan.mdto note Declarative conflict-resolution file for recurring third-party source conflicts #153's own step 6 and rule-fileformat build on this issue's shipped format rather than inventing a new one — a scope note only,
not a rewrite of Declarative conflict-resolution file for recurring third-party source conflicts #153's remaining generation/staleness/endpoint work. Already done as part of
Declarative conflict-resolution file for recurring third-party source conflicts #153's own plan doc rewrite (2026-07-25); re-confirm the shipped shape still matches once this
issue is actually implemented.
Expected tests
Quotinator.Core.TestsPlanAsync_MatchingRuleExists_AutoResolvesWithoutPendingQuotinator.Core.TestsPlanAsync_NoMatchingRule_StagesPendingAsTodayQuotinator.Core.TestsSeedNikhilNamal17_AllNineKnownConflicts_AutoResolveViaRuleFileQuotinator.Core.TestsSeedVilaboim_ReviewPolicy_NoStagedActions, plus equivalents forquotinator-curatedandquotinator-series-universe(widened from vilaboim-only)The first two tests are the same ones #153 lists in its own Expected tests table — implementing this
issue ships the mechanism they exercise, so #153 inherits them as already-passing regression guards
rather than writing them fresh when #153 itself lands.
Definition of done