Audit — relations utiles entre users, stores, organisations, profils
Demande directe, pas une rotation @codebase-auditor. Lecture seule sur le code, sauf un constat corrige dans la meme session (voir « Ce qui a ete corrige »). Perimetre : toute primitive relationnelle entre User, Store…
Demande directe, pas une rotation
@codebase-auditor. Lecture seule sur le code, sauf un constat corrige dans la meme session (voir « Ce qui a ete corrige »).Perimetre : toute primitive relationnelle entre
User,Store,Organization,OrganizationMember, et les profils publics (Watchlist,StoreTracker,IntelligenceWatchlist,username,leaderboardOptOut,NetworkApplication, forum, seller profile). Permissions et confidentialite explicitement hors perimetre (deja couvertes ailleurs —getStoreAccess/hasAccessPermission,src/lib/security/).Etat du depot au moment du constat :
origin/maina235d1f1b(11 septembre 2026, ~01:45 UTC). Le correctif ci-dessous a merge en #1034.
Objectif
Trouver les primitives relationnelles reelles mais mal ou pas cablees, les connecter sans en inventer de nouvelles, pour que l'arrivee de plus d'utilisateurs et de stores rende l'ecosysteme progressivement plus utile — sans transformer artificiellement le produit en reseau social.
Ce qui a ete corrige
IntelligenceWatchlist — un modele avec zero ecrivain, jamais
git log -S sur tout l'historique : aucun create/upsert/createMany/
$executeRaw n'a jamais existe pour ce modele. /[orgSlug]/intelligence
le lisait et rendait donc l'etat vide pour toujours — un etat vide qui
promettait en plus un outil @Atlas « watch » qui n'existe pas non plus
dans src/features/ai/tools/.
La vraie primitive, ecrite et lue par deux crons (prediction-fanout,
tracker-anomaly-fanout), est StoreTracker — per-store, posee par le
bouton « Track in Brand Tracker » du Store Spy, via
/api/intelligence/hub/tracker. Elle n'etait simplement jamais agregee
au niveau organisation, alors que getStoreAccess derive deja entierement
de getOrgAccess (pas d'ACL par store) : agreger les trackers de tous les
stores d'une org n'expose donc rien qu'un membre ne pouvait deja voir
store par store.
Correctif (#1034,
pilier intelligence) : rollupOrgTrackers() aplati les StoreTracker
de tous les stores d'une org en une liste dedupliquee par domaine (les
deux stores qui suivent le meme concurrent produisent une entree, avec les
deux attributions et la date d'ajout la plus ancienne). /[orgSlug]/intelligence
lit desormais ca. Le modele mort et sa relation Organization sont
retires du schema.
Ce qui a ete verifie et n'est PAS un constat
Quatre pistes qui ressemblaient au meme defaut, refutees a la ligne :
| Piste | Verdict |
|---|---|
User.username — unique, collecte a l'onboarding | Cosmetique uniquement (@username dans le shell). Rien ne le lit pour une relation. Pas casse : n'a jamais promis plus. |
Watchlist.itemType === "store" | Suspecte morte au premier grep (aucune occurrence litterale itemType="store"). En realite cablee via <FollowButton itemType={type.toLowerCase()} …> sur /marketplace/[type]/[slug], ou type est MarketplaceType — donc vivante, juste dynamique. |
NetworkApplication / /network | Formulaire de lead (affiliate/agency/referral), pas une primitive relationnelle. /network est une vitrine marketing statique, pas un annuaire d'utilisateurs reels. |
User.leaderboardOptOut | La copie UI (messages/en.json, dashboard.account.privacy.leaderboardDescription) promet exactement « votre nom et avatar s'affichent sur /community/levels » — et services/community/xp.ts:leaderboard() masque bien name/image quand optedOut. La promesse est tenue a la lettre : rien a corriger. |
Ce qui reste une piste, pas un bug
Deux surfaces auraient un gain reel a etre connectees, mais aucune n'est une primitive existante mal cablee — construire l'une ou l'autre inventerait une nouvelle surface produit (un profil public generique), ce qui sort du mandat de cet audit :
- Auteur du forum non cliquable —
ForumThread.authoretForumPost.authorrendent nom + avatar (services/community/forum.ts) sans lien. Le seul profil public existant,/sellers/[id](services/marketplace/sellers.ts), rendnullpour quiconque n'a pas de listing marketplace LIVE — y lier l'auteur casserait pour la quasi- totalite des membres du forum plutot que de les connecter. - Activite par membre d'organisation —
/[orgSlug]/~/membersest un roster (nom, email, role, date d'entree), sans vue croisee « qui a fait quoi ».PlatformActivityporte dejaactor(userId) +orgId, donc la donnee existe ; aucune page ne la projette par membre aujourd'hui.
Les deux demandent un vrai profil ou une vraie vue d'activite par utilisateur — une decision produit (jusqu'ou aller vers de l'identite publique), pas une correction de cablage. Documente ici plutot que construit, sur decision explicite au moment de cet audit.
Perimetre couvert, methode
Inspection directe (grep/lecture) de chaque modele relationnel du
schema, verification de chaque FollowButton/lien d'auteur/route
publique par son ou ses vrais appelants (jamais par le nom du fichier ou
un commentaire), et verification croisee des trois primitives de
« watch » du schema (Watchlist, StoreTracker, IntelligenceWatchlist)
avant de conclure laquelle etait reellement vivante. Pas de fan-out par
sous-agents — un seul passage, execute et verifie de bout en bout
(tests, guards, merge, push, PR mergee).