Kategorie-Wissensbasis pro Einkaufsliste statt global scopen #178
Labels
No labels
priority/could
priority/must
priority/should
priority/wont
status/blocked
status/claimed
status/done-migrated
type/bug
type/feature
type/infra
type/tech-debt
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
robert/todo#178
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Story: Kategorie-Wissensbasis pro Einkaufsliste statt global scopen
As a Nutzer eines Haushalts mit eigener Einkaufsliste,
I want to dass gelernte Produkt-zu-Kategorie-Zuordnungen nur innerhalb meiner eigenen Liste gelten,
so that die Kategorie-Vorschläge zu meinem eigenen Kategorie-System passen, statt von fremden Haushalten mit anderen Kategorie-Namen beeinflusst zu werden.
Kontext (verifiziert im Code):
ProductSectionKnowledgeEntityist aktuell app-weit, nicht listen-gebunden — eigener Kommentar im Code: "app-wide (not list-scoped) product-name -> generic-section-name knowledge base ... grown by every user who adds a genuinely new product to any shopping list". Felder: nurId,ProductName,SectionName— keinShoppingListId, keinUserId, nicht mal ein Unique-Index aufProductName(bewusst, um mehrere Sektionsnamen pro Produkt zu erlauben).ShoppingCategoryResolver.cssResolveFromKnowledgeBaseliest ohne jede Filterung nach Liste/Nutzer aus der gesamten Tabelle;ContributeToKnowledgeBaseschreibt ebenso ungefiltert global.useCategorySuggestions/useUncategorizedCategorySuggestions) — betrifft diese Story nicht direkt.Acceptance criteria:
ProductSectionKnowledgeEntitybekommt eineShoppingListId-Zuordnung (Migration erforderlich).ResolveFromKnowledgeBaseschlägt nur noch Sektionen vor, die aus derselben Einkaufsliste gelernt wurden.ContributeToKnowledgeBaseschreibt neue Zuordnungen mit derShoppingListIdder Liste, auf der das Produkt tatsächlich angelegt wurde.Out of scope for this story:
useCategorySuggestions) — unverändert, betrifft ein anderes System.Open questions: (escalate to human if unanswered)
Claimed. Starting implementation.
Architect decision on the open migration question (no cross-agent disagreement, resolving without human escalation per the escalation criteria in 00_team_overview.md):
Existing global
ProductSectionKnowledgeEntityrows are discarded, not seeded per list. The entity has noShoppingListIdon any existing row, so there is no factual basis to attribute a historical entry to one particular list over another - any seeding choice (e.g. "seed into every list") would fabricate list-specific history that never actually happened on that list, and directly contradicts this story's own accepted trade-off that a list without its own learned history gets no suggestions. Discarding is also the simpler, lower-risk migration (drop rows in the same migration that adds the FK, no name-matching heuristics, no risk of silently misattributing a CSV-seeded generic entry to the wrong household's list).Plan: add
ShoppingListId(non-nullable) toProductSectionKnowledgeEntityvia migration that also truncates existing rows; scopeResolveFromKnowledgeBase/ContributeToKnowledgeBaseinShoppingCategoryResolver.csto the current list; add handler + component tests.Done, pushed as
ba32410.Scope:
ProductSectionKnowledgeEntity(product-name -> category-section-name auto-categorization knowledge base) is now scoped perShoppingListIdinstead of app-wide.Changes:
ProductSectionKnowledgeEntitygets a requiredShoppingListIdFK (cascade-delete with the list), mirroringShoppingCategoryEntity's existing pattern.ShoppingCategoryResolver.ResolveFromKnowledgeBase/ContributeToKnowledgeBaseboth filter reads and writes by the caller'sShoppingListId- already authorized upstream viaAuthorizeShoppingListAccessForCurrentUserQueryonCreateShoppingProductCommand.AddShoppingListIdToProductSectionKnowledgedeletes existing rows (including the #90 CSV seed) before adding the FK - see the earlier comment on this issue for why (no existing row carries any list attribution to migrate from, and the story's own AC already accepts a list with no learned history getting no suggestions).Tests: updated existing
CreateShoppingProductCommandHandlerTeststo setShoppingListIdon inserted knowledge rows, added a new test (Does_not_use_a_knowledge_base_entry_learned_on_a_different_list) proving cross-list leakage no longer happens, and asserted the contributed row carries the correctShoppingListId.Verification:
dotnet build Cqs.sln -c Release(0 errors) and Docker-freeCommon.Tests(119/119) +ReactUibuild both green locally. Docker was unavailable in this session (confirmed viadocker.exe versiontiming out), so the Testcontainers-backedCheckly.Testsfor this change - including the new list-scoping test - could not run locally; relying on the real CI run for that (git.butzei.de/robert/todo/actions), which was still queued behind other pushes at push time (FIFO runner queue under concurrent loop sessions, per existing team-memory note - not a build failure). Self-review + thesecurity-reviewskill both ran clean (the change narrows a pre-existing cross-tenant read/write, doesn't introduce one).Out of scope (per the story): the client-side embedding-similarity suggestion badge (
useCategorySuggestions) and the pantry, which has no server-side equivalent - both untouched.Correction from direct human feedback (2026-09-07), reverted in
f7ead53.The per-list isolation implemented above was the wrong direction. Actual product intent, stated directly: every shopping list (new or existing) should keep drawing suggestions from the full pooled knowledge base - the 200 pre-seeded CSV categories plus everything learned across every hosted list - specifically to minimize manual categorization work, not to wall lists off from each other's history.
f7ead53cleanly revertsba32410(ProductSectionKnowledgeEntity/ShoppingCategoryResolver/CreateShoppingProductCommandHandler/tests/comment all restored byte-identical to their pre-#178 content, verified viagit diff) and drops theAddShoppingListIdToProductSectionKnowledgemigration outright rather than adding a compensating one - confirmed safe because it was never applied anywhere (CI was still queued and Docker was unavailable in this environment for the entire ~15 minutes between the two commits).Net effect: the app-wide knowledge base from #90 is unchanged/restored. Closing this back out - no further action pending. If per-list customization is wanted in the future, it needs a fresh story with this corrected premise (pooled base, not isolated).
Erneut aufgegriffen im Rahmen von #190 (2026-09-12), mit fresh direktem Feedback vom Menschen - keine Neu-Verhandlung der damaligen Entscheidung, sondern eine bewusst andere.
Zur Erinnerung: dieses Issue wurde ursprünglich als "pro Einkaufsliste" implementiert (
ba32410), dann nach direktem Feedback wieder auf "app-weit geteilt" zurückgesetzt (f7ead53), mit der Begründung, jede Liste solle weiter von der vollen gemeinsamen Wissensbasis profitieren.Im Rahmen von #190 (geteilte Barcode-Wissensbasis) wurde dieselbe Grundfrage für ein eng verwandtes System erneut gestellt. Diesmal lautet die explizite Entscheidung: weder rein app-weit noch rein pro-Liste, sondern geteilt innerhalb der Kombination aus Einkaufsliste + der mit ihr verknüpften Vorratsschrank-Liste - dieselbe Holzhausen-Grenze, die #174 für Kategorien bereits etabliert hat.
ProductSectionKnowledgeEntity(dieses Issue) bekommt dieselbe Behandlung wie die neueProductBarcodeKnowledgeEntity(#190): eineShoppingListId-Spalte, Lookups/Writes inShoppingCategoryResolverentsprechend gefiltert. Bereits bestehende, app-weit gesammelte Einträge werden verworfen (keine faktische Zuordnungsgrundlage zu einer bestimmten Liste) - jede Liste lernt Namens-Kategorie-Zuordnungen ab jetzt wieder neu, dafür geteilt mit ihrer eigenen Vorratsschrank-Liste statt mit fremden Haushalten.Umgesetzt und gepusht als Teil von
9cf7f088(#190s Backend-Commit). Migration20260911224536_AddShoppingListIdToProductSectionKnowledge.