Audits · août 2026Audit — la regle des quatre portes, sur tous les systemes

Audit — la regle des quatre portes, sur tous les systemes

Sortie de inbox/0085, decoupe 6/6 de inbox/0073. Lecture seule sur le code. Ce document, scripts/four-doors.mjs et les items de backlog qu'il ouvre sont la sortie complete. Etat du depot : branche…

Sortie de inbox/0085, decoupe 6/6 de inbox/0073.

Lecture seule sur le code. Ce document, scripts/four-doors.mjs et les items de backlog qu'il ouvre sont la sortie complete.

Etat du depot : branche claude/notion-hub-access-9kqa19, 28 aout 2026.

La regle mesuree

Tout ce qui vit dans la codebase doit etre pilotable a la fois via le MCP, via l'API, via le Chat cote utilisateur, et via le panel Admin/Team cote agence, et rien ne doit pourrir dans un coin sans etre mis en avant, utile, ou relie.

0073 l'avait mesuree sur deux systemes. Celui-ci la mesure sur vingt-quatre.

Le tableau

Genere par node scripts/four-doors.mjs. Il n'est pas recopie a la main : si un chiffre ci-dessous ne correspond plus, c'est la commande qui fait foi.

system            panel  cron   api   chat   mcp
────────────────────────────────────────────────────
studio             9     1     1     ✓     ·
radar-bulletin     2     4     5     ·     ·
growth             1     2     ·     ·     ·
intelligence       8    19    33     ✓     ·
marketplace       28     2    45     ·     ·
tracking           3     1     5    ag     ·
vitals             1     4     6     ✓     ·
aeo                1     2     ·     ✓     ·
cro                1     ·     ·     ✓     ·
aov                ·     1     ·     ✓     ·
commerce           ·     3     ·     ✓     ·
action-plan        ·     ·     ·     ✓     ·
status             1     2     3     ·     ·
knowledge          ·     ·     2     ✓     ·
theme              ·     ·     1    ag     1
connectors         4     2    28     ✓     ·
store-core        19     1    40     ✓     6
organizations     30     ·    21     ✓     ·
billing            9     7     9     ·     ·
agents             4     ·     4     ·     ·
workflow           2     1     4     ·     ·
memory             3     1     5     ·     ·
browser            ·     1     7    ag     ·
chat-runtime       3     ·    15     ·     ·
────────────────────────────────────────────────────
counted: 141 panel pages, 58 crons, 306 api routes (crons excluded),
         19 registered tool factories, 7 mcp scopes

· case vide. ag porte chat existante uniquement dans la couche agent : une boutique connectee sans agent complet ne l'a pas.

Ce bloc est le releve du jour de l'audit, laisse tel quel : c'est la preuve des neuf items ci-dessous, et la reecrire au fil des livraisons effacerait la raison pour laquelle ils existent. Pour l'etat courant, relancer la commande — les cases livrees depuis (memory, agents, workflow, marketplace, growth et radar-bulletin en chat, knowledge et aov en panel) y apparaissent pleines.

Trois cases de ce releve etaient fausses le jour meme, et pas parce que le compte etait mal fait : aov, commerce et action-plan portaient panel: [] dans SYSTEMS, une affirmation ecrite a la main. commerce avait [orgSlug]/[storeSlug]/insights et action-plan [orgSlug]/[storeSlug]/intelligence depuis le debut. Le second garde-fou decrit plus bas ne pouvait pas le voir : store-core reclame le prefixe [orgSlug]/[storeSlug], donc aucune page de boutique n'est jamais « non reclamee ». Les declarations sont corrigees ; l'angle mort qui les a laissees fausses est ouvert en 0110.

La methode, et pourquoi elle n'est pas un grep

0085 posait la contrainte principale : deriver, ne pas grepper un mot, parce que intelligence-tools.ts dit « radar » deux fois au sens metaphorique. Chaque colonne est donc comptee sur une structure, jamais sur un nom :

ColonneComptee sur
panelles page.tsx sous src/app/(dashboard)/
cronles entrees crons de vercel.json
apiles route.ts sous src/app/api/, hors cron/
chatles fabriques que createAtlasTools appelle, dans src/features/ai/tools/index.ts
mcples ids de MCP_SCOPES

Une seule chose est ecrite a la main : le tableau SYSTEMS, qui dit ce que chaque systeme est — ses prefixes de routes, ses prefixes de cron, ses fabriques d'outils, ses pages de panel, ses scopes. C'est une affirmation sur l'architecture, aucun script ne peut la deriver.

Trois choix de mesure ont change le resultat, et meritent d'etre lus :

La porte chat est le registre, pas le repertoire. Un fichier dans src/features/ai/tools/ est un module d'outils le jour ou quelqu'un l'ecrit ; il est une porte le jour ou createAtlasTools l'etale. Compter les fichiers aurait donne types.ts, fence.ts, permissions.ts et studio-tools.test.ts pour des portes — et aurait continue a compter un module qu'on aurait debranche, ce qui est exactement l'etat que cet audit existe pour trouver. Le compte passe ainsi de 21 fichiers a 19 fabriques reellement enregistrees.

Un cron appartient a un systeme par prefixe de chemin complet. La premiere version coupait le dernier segment sur -, ce qui rangeait intelligence/refresh-hot dans une famille « refresh ». Treize crons d'intelligence n'appartenaient alors a personne : la colonne affichait 4 au lieu de 19.

La colonne MCP a d'abord produit un faux positif sur son propre auteur. Le premier jet appariait un scope a un systeme par sous-chaine : theme attrapait boostecom:themes.read. C'est litteralement le piege que 0085 decrit, reproduit dans le script cense le mesurer. Verification faite, l'appariement etait juste par accident — boostecom:themes.read ouvre listThemes, getTheme, runAudit, qui sont bien les outils de theme-tools.ts — mais un resultat juste par accident n'est pas un resultat. Les scopes sont desormais declares systeme par systeme.

Deux garde-fous, qui sont le fond du script

Un script d'audit ment de deux facons, et les deux sont silencieuses.

Une affirmation qui ne correspond a rien est signalee, jamais comptee zero. Un prefixe devenu obsolete afficherait une case vide, une case vide se lit comme un manque, et l'audit fabriquerait du travail a partir de sa propre faute de frappe. Le script sort en 1 sur ce cas.

Ce que personne ne reclame est affiche. Sans quoi le tableau est un sous-ensemble flatteur : on obtient toujours un bon score sur les systemes qu'on a pense a lister. C'est ce garde-fou qui a trouve les quatre derniers systemes du tableau — agents, workflow, memory, browser —, absents de ma premiere redaction a la main.

Ce qui reste non reclame aujourd'hui est de la plomberie assumee : weekly-digest, prune-audit-log, cleanup-drafts, weekly-audits cote cron, et une cinquantaine de routes d'infrastructure (auth/**, webhooks/**, health/**, upload, logs, security/csp-report…). Une route d'authentification n'a pas a etre un systeme pilotable ; elle est listee pour que le tableau ne devienne pas un sous-ensemble sans qu'on le voie.

Verifie par mutation, et deliberement hors CI

Un script d'audit dont personne n'a essaye de casser les garde-fous est un script d'audit qui affirme. Cinq mutations, cinq observees :

MutationCe qui doit arriverObserve
un prefixe d'API devenu obsoleteBROKEN, sortie 1sortie 1
une fabrique d'outils jamais enregistreeBROKEN, sortie 1sortie 1
le prefixe de cron intelligence retirela colonne tombe et les 14 crons orphelins apparaissent19 -> 5, les 14 listes
l'exclusion de createAtlasTools retireela fonction se compte elle-meme19 -> 20, listee non reclamee
le systeme browser retire du tableauson cron et sa fabrique apparaissent non reclamesles deux listes

Les trois dernieres sont celles qui comptent : elles prouvent que le rapport « non reclame » voit reellement ce que le tableau cacherait. La troisieme est exactement le bug qu'avait la premiere version du script.

Ce script n'est pas branche sur la CI, volontairement. Les autres documents de docs/audits/ sont des instantanes dates, pas des contrats : c'est pnpm docs:claims qui tient les affirmations vivantes de CLAUDE.md. Brancher celui-ci ferait d'une route supprimee un echec de build au nom d'une photo prise le 28 aout. La commande se relance a la demande, et elle sort en 1 quand sa carte ment — ce qui est la garantie utile.

Les cases vides legitimes

0085 etait explicite : « une case vide n'est pas automatiquement un manque […] sinon il fabrique douze items sans valeur. » Voici celles qui n'en sont pas.

Toute la colonne MCP — un seul fait, pas quinze items

L'en-tete de src/lib/security/mcp-scopes.ts le dit sans ambiguite : le catalogue est « un nom lisible pour un groupe d'outils qui partagent deja une exigence de scope Shopify », avec la Custom App du marchand pour plafond. Les sept scopes gouvernent les douze outils du relais Shopify.

Aucun systeme dont BoostEcom est proprietaire n'est joignable en MCP : ni Studio, ni Radar, ni Marketplace, ni Intelligence, ni Tracking, ni Vitals, ni AEO, CRO, AOV, Commerce, Knowledge, Memory, Workflow.

C'est un fait d'architecture, pas quinze manques. Il a deja son item — integrations/0083, que growth-web/0091 nomme comme son bloqueur. Quinze items « ajouter les scopes MCP de X » seraient exactement les douze items sans valeur contre lesquels 0085 previent, avec un chapeau different.

La colonne cron n'est pas une porte

Elle vient de 0073 et elle est utile pour situer un systeme, mais la regle enonce quatre portes : MCP, API, Chat, Panel. « Pas de cron » veut dire « rien de recurrent a faire », ce qui est une propriete du systeme, pas un manque. Aucun item n'est ouvert pour une case cron vide.

Trois portes chat volontairement absentes

  • status — une page d'etat publique et sa console d'incidents. Qu'un marchand demande a son assistant comment va notre plateforme n'est pas un besoin produit, et la reponse est deja a une page non authentifiee.
  • billing — depenser de l'argent dans un tour de chat est la seule action que les garde-fous de credits existent pour garder deliberee : l'estimation pre-stream et le plafond dur mid-stream supposent tous deux que le tour n'est pas ce qui achete les credits.
  • chat-runtime — c'est la porte. Un outil qui pilote le chat depuis le chat est une boucle, pas une surface.

Le panel absent de browser

Les outils navigateur sont une primitive du cockpit, pas une destination : les routes preview/browserbase/** sont consommees par le workspace du store, qui est leur ecran. Un ecran « sessions de navigateur » n'aurait d'audience que nous.

Les cases vides reelles

Neuf items, en groupant les cases dont la decision est la meme decision plutot qu'en en recopiant une par cellule.

#Case videPilierItem
1chat — radar + growthai-platform0093 la primitive, puis 0103 les portes
2chat — marketplaceai-platform0094
3chat — memoryai-platform0095
4chat — agents + workflowai-platform0096
5panel — knowledgeai-platform0097
6api — growthgrowth-web0098
7api — aeo, cro, aov, commercecommerce-systems0099
8panel — aov seulcommerce-systems0100, et 0110 pour la cause
9panel — themeapp-shell0101

Deux observations que le tableau rend visibles et qu'aucune lecture ne donnait :

Cinq cases chat vides, une seule cause pour deux d'entre elles. 0093 a livre cette cause le jour meme ; les deux cases restent vides jusqu'a 0103, qui ecrit les outils. Une ligne de ce tableau nomme donc deux items la ou les autres en nomment un : la primitive et la porte ne sont pas le meme travail, et les confondre ferait disparaitre le second. AtlasToolContext.userRole vaut owner | admin | member | viewer : c'est le role dans l'organisation, pas User.role === "ADMIN". La couche outils n'a aucune notion d'admin plateforme. Radar et Growth sont globaux, donc leur porte chat n'est pas un travail d'ecriture d'outils — elle attend cette primitive. Marketplace, Memory, Agents et Workflow sont scopes par organisation et ne l'attendent pas : c'est pour cela qu'ils sont des items distincts et non des dependances du meme.

Quatre systemes de boutique n'ont aucune surface HTTP. AEO, CRO, AOV et Commerce ont une porte chat et, pour deux d'entre eux, une page. Leurs freres immediats en ont une : vitals a six routes, tracking cinq. Cette asymetrie entre systemes de meme nature est la preuve, pas le raisonnement.

Trois systemes ont une porte chat et rien a regarder. AOV, Commerce et Action Plan : le marchand peut demander a Atlas son panier moyen, sa retention par cohorte, sa liste priorisee d'actions — et n'a aucun ecran ou la revoir. Action Plan est le cas le plus net : « quoi faire ensuite » est la synthese que la plateforme vend, et elle n'existe que dans un tour de chat qui defile.

Ce que cet audit ne dit pas

Il mesure l'existence d'une porte, jamais sa qualite. Une porte API qui rend un objet pauvre compte comme une porte. C'est la meme frontiere assumee que src/test/api-authorization-coverage.test.ts : prouver qu'une route a un garde n'est pas prouver que le garde refuse.

0097 en est la demonstration. La case panel — knowledge etait vide et elle avait raison de l'etre : la page de route que le composant decrivait dans son propre docstring n'avait jamais ete commitee, et le seul acces aux sources de connaissance etait un tiroir ouvert depuis un noeud de canvas. Mais le fait interessant etait invisible pour un tableau qui compte des portes — ce tiroir inventait ses reponses. Ajouter une source ne touchait jamais l'API ; « synchroniser » ecrivait indexed: true a cote d'un handler dont le commentaire disait « for v0.0.1: mark as indexed » et qui n'embarquait rien ; supprimer retirait la ligne en laissant ses chunks interrogeables par @Atlas, deleteKnowledgeSource n'ayant aucun appelant malgre un docstring affirmant le contraire. Une porte qui ment compte comme une porte.

Il ne mesure pas non plus la seconde moitie de la regle — « rien ne doit pourrir dans un coin sans etre mis en avant, utile, ou relie ». Un systeme peut avoir ses quatre portes et rester introuvable dans la navigation.