Skip to content

TASK-1614 — Catalogue plugin « 3 assistants IA » + disponibilité Organization - #615

Merged
cslucki merged 3 commits into
developfrom
TASK-1614-catalogue-plugin-3-assistants-ia-disponibilite-organization
Sep 21, 2026
Merged

cslucki merged 3 commits into
developfrom
TASK-1614-catalogue-plugin-3-assistants-ia-disponibilite-organization

Conversation

@cslucki

@cslucki cslucki commented Sep 21, 2026

Copy link
Copy Markdown
Owner

SLICE A — Catalogue plugin + disponibilité Organization

Premier lot de la Product Spec V0 « ChatLoop — 3 assistants IA ».

Cette PR rend possible une seule phrase, et rien de plus :

« ce plugin existe dans le catalogue plateforme, et il est autorisé ou non
pour telle Organization. »

Aucun assistant, aucun prompt, aucune capability, aucun appel provider, aucune
activation dans une Boucle.

Architecture

Catalogue plateforme  (config/loop_plugins.php)
        ↓
Disponibilité Organization  (organization_loop_plugins)
        ↓
[SLICE B] Activation dans une Boucle
        ↓
[SLICE C/D/E] Assistants, économie, Evidence, UX

Le catalogue est un fichier, pas une table. Patron déjà établi par
config/loop_types.php + LoopTypeRegistry : un deuxième plugin doit rester
une entrée de configuration. Une table de catalogue aurait demandé une
migration, un CRUD et un écran d'édition pour décrire ce que seul un
déploiement peut de toute façon livrer.

La disponibilité est une table, parce que c'est une décision, qu'elle
change sans déploiement, et qu'elle est toujours celle d'une Organization.

Deux décisions qui méritent d'être lues

organization_id NOT NULL. loop_type_settings l'accepte nullable parce
qu'un réglage de type existe à deux portées et que null y veut dire « la
Plateforme ». Ici il n'y a qu'une portée. Un null voudrait dire « disponible
partout » — exactement le contraire de ce qu'une capacité expérimentale doit
faire quand personne n'a rien décidé. Fermé par défaut.

Pas de trait HasOrganizationId sur le modèle. Ce trait remplit
organization_id depuis current_organization quand il est absent. C'est
juste pour une écriture faite dans un tenant ; c'est faux ici : le SuperAdmin
écrit pour l'Organization qu'il désigne, et elle n'a aucune raison d'être
celle de sa propre session. Hérité en silence, ce serait la faute inter-tenant
exacte que cette PR doit rendre impossible.

Éteindre n'efface pas la ligne. available = false conserve updated_by
et updated_at — donc qui a coupé et quand. C'est l'écart assumé avec
loop_type_settings, qui revient au défaut en supprimant : pour une capacité
expérimentale qu'on doit pouvoir retirer vite, c'est précisément la trace
qu'on veut garder.

Tests — 21 verts, garde tenant mesurée

tests/Feature/TASK1614LoopPluginCatalogueTest.php — 21 passed (52 assertions).

Les 5 tests tenant sont assertés dans les deux sens (A→B et B→A) : un seul
sens laisserait passer une lecture qui ignore purement et simplement
l'organization_id.

Sabotage joué — retirer ->where('organization_id', …) de isAvailable() :

⨯ activer dans a ne rend rien disponible dans b
⨯ activer dans b ne rend rien disponible dans a
⨯ eteindre dans a n eteint pas b
Tests: 3 failed, 18 passed

La garde est donc mesurée, pas affirmée. Arbre restauré, git diff vide.

Voisins — 205 verts, grepés sur le contrat touché (admin.loop-types,
admin.loop-permissions, layouts.admin, Route::getRoutes()) : T1079 ×2,
T1116, T1117, T1118, T1119, T1489, T1392, T07411, T1289, tests/Unit/Architecture.

Recette navigateur — https://test.laravel/admin/loop-plugins

Étape Constat
Ouverture « 3 assistants IA », clé multi_ai_assistants, badge EXPERIMENTAL, 9 Organizations, 0 sur 9, toutes « Non autorisé »
« Autoriser » sur BouclePro bandeau de succès, ligne → Autorisé + « Modifié le 21/09/2026 10:08 par Admin », 1 sur 9
Les 8 autres restent « Non autorisé » — un seul bouton « Retirer » sur neuf lignes
Base 1 seule ligne écrite
« Retirer » 0 sur 9, ligne conservée avec available=false et sa trace
Rechargement état relu depuis la base
Console 0 erreur

Non vérifié

Responsive. La fenêtre a été redimensionnée en 390×844 mais le viewport
rendu est resté à 1920×812 : le rendu mobile n'a pas été constaté. Le
balisage reprend le patron des écrans admin voisins, mais c'est une
ressemblance de code, pas une mesure.

Hors scope préservé

loop_ai_assistants, Aperio/Traverse/Limen, prompts, loop_multi_ai,
AiEconomicGuard, config/ai_pricing.php, OpenRouter, Evidence Build,
AiTurnLock, follow-ups, UX ChatLoop, permissions owner/animateur — aucun
touché. Les hard gates du §9 de la Product Spec portent sur l'activation en
production : ils restent dus aux SLICES C et D.

Aucune logique Community. Aucune migration Community → Organization.

VERSION 1.614.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JqwjarKwhSbpzdPxLAmNh3

@cslucki cslucki added the validation:sensitive TASK-1150 : charge CI complete obligatoire (auth/tenant/migration/securite) label Sep 21, 2026
Cyril and others added 3 commits September 21, 2026 13:57
…zation (TASK-1614)

SLICE A de la Product Spec V0 « ChatLoop — 3 assistants IA », validee
repo-grounded par OPUS et passee a VALIDATED sur cette branche.

Ce que cette TASK rend possible, et rien de plus :

    « ce plugin existe au catalogue plateforme, et il est autorise ou
     non pour telle Organization. »

Le CATALOGUE est un fichier (config/loop_plugins.php), pas une table :
un deuxieme plugin doit rester une entree de configuration, comme un
cinquieme type de Boucle l'est dans config/loop_types.php.

La DISPONIBILITE est une table (organization_loop_plugins), parce que
c'est une decision, qu'elle change sans deploiement et qu'elle est
toujours celle d'UNE Organization. organization_id y est NOT NULL :
loop_type_settings l'accepte nullable parce qu'un reglage de type existe
a deux portees et que null y veut dire « la Plateforme » ; ici un null
voudrait dire « disponible partout », soit le contraire de ce qu'une
capacite experimentale doit faire quand personne n'a rien decide.

Ferme par defaut. Eteindre n'efface pas la ligne : available=false
conserve qui a coupe et quand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqwjarKwhSbpzdPxLAmNh3
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqwjarKwhSbpzdPxLAmNh3
…tre du cycle de vie (TASK-1614)

CI rouge, shard Feature 3/6 :
UserDataLifecycleRegistryTest::test_every_user_foreign_key_is_declared_in_lifecycle_registry
-> 'Every FK to users.id must be classified in UserDataLifecycleRegistry.'

La migration ajoute une cle etrangere vers users.id (updated_by, qui porte
l'auteur de la decision de disponibilite). Le depot tient un registre
DECLARATIF de toutes ces cles : une cle non classee est une donnee
utilisateur dont personne n'a dit ce qu'elle devient a la suppression du
compte. La garde a fait exactement son travail.

Politique DETACH, org_scope direct, sur le modele de
custom_loop_types.created_by et ai_credit_setting_changes.changed_by : savoir
si un plugin est autorise dans une Organization est une decision
d'administration, pas une donnee personnelle. Elle doit survivre a son auteur,
qui est simplement detache — la cle est deja nullOnDelete.

Mesure : 30 verts en PostgreSQL (UserDataLifecycle + TASK1614).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqwjarKwhSbpzdPxLAmNh3
@cslucki
cslucki force-pushed the TASK-1614-catalogue-plugin-3-assistants-ia-disponibilite-organization branch from 9cef034 to 87996e5 Compare September 21, 2026 12:06
@cslucki
cslucki merged commit 45eb437 into develop Sep 21, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

validation:sensitive TASK-1150 : charge CI complete obligatoire (auth/tenant/migration/securite)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant