Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) · Versioning

### Added

- **api+web/sso-zelian** — SSO OIDC via le **serveur OAuth 2.1 de Supabase Auth** (feature Enterprise), réimplémenté en CE **clean-room à partir du provider `gitea`** (jamais de copie plane-ee) — **zéro migration**. Backend : `ZelianOAuthProvider` (`provider/oauth/zelian.py`) avec **PKCE S256** + **`client_secret_basic`** au token endpoint et mapping userinfo Supabase (`sub`→provider_id, `name`→prénom/nom, `picture`→avatar) ; endpoints app + space (`views/{app,space}/zelian.py`, `generate_pkce_pair()` — `code_verifier`/`state` en session) ; 4 routes (`/auth/zelian/[callback/]`, `/auth/spaces/zelian/[callback/]`) ; codes d'erreur `ZELIAN_NOT_CONFIGURED=5113` / `ZELIAN_OAUTH_PROVIDER_ERROR=5124` ; `is_zelian_enabled` exposé sur `/api/instances/` (piloté par `IS_ZELIAN_ENABLED`). Frontend : bouton « Continue with Zelian » (web + space) via les **seams d'extension** (`hooks/oauth/extended.tsx`, `TExtendedLoginMediums="zelian"`, `EXTENDED_LOGIN_MEDIUM_LABELS`) — **zéro modif de fichier core** ; type `is_zelian_enabled`, logo. Config par env (`ZELIAN_AUTH_BASE_URL`/`CLIENT_ID`/`CLIENT_SECRET`). Vérifié **offline** : 9 tests unitaires (PKCE S256, URL authorize, Basic auth + `code_verifier`, mapping userinfo), résolution des 4 routes, `/api/instances/` expose le flag, ruff clean, turbo `check:types` web+space 12/12. ⚠️ **Validation E2E bloquée** tant que la config Supabase (dashboard OAuth Server + client + page `/oauth/consent` externe) n'est pas fournie — cf. `PLAN-SSO-SUPABASE-PLANE.md` §1-3. Hors scope v1 : auto-redirect « sans clic » (§6, touche un fichier core → follow-up).
- **api+web/sso-zelian** — SSO OIDC via le **serveur OAuth 2.1 de Supabase Auth** (feature Enterprise), réimplémenté en CE **clean-room à partir du provider `gitea`** (jamais de copie plane-ee) — **zéro migration**. Backend : `ZelianOAuthProvider` (`provider/oauth/zelian.py`) avec **PKCE S256** + **`client_secret_basic`** au token endpoint et mapping userinfo Supabase (`sub`→provider_id, `name`→prénom/nom, `picture`→avatar) ; endpoints app + space (`views/{app,space}/zelian.py`, `generate_pkce_pair()` — `code_verifier`/`state` en session) ; 4 routes (`/auth/zelian/[callback/]`, `/auth/spaces/zelian/[callback/]`) ; codes d'erreur `ZELIAN_NOT_CONFIGURED=5113` / `ZELIAN_OAUTH_PROVIDER_ERROR=5910` ; `is_zelian_enabled` exposé sur `/api/instances/` (piloté par `IS_ZELIAN_ENABLED`). Frontend : bouton « Continue with Zelian » (web + space) via les **seams d'extension** (`hooks/oauth/extended.tsx`, `TExtendedLoginMediums="zelian"`, `EXTENDED_LOGIN_MEDIUM_LABELS`) — **zéro modif de fichier core** ; type `is_zelian_enabled`, logo. Config par env (`ZELIAN_AUTH_BASE_URL`/`CLIENT_ID`/`CLIENT_SECRET`). Vérifié **offline** : 9 tests unitaires (PKCE S256, URL authorize, Basic auth + `code_verifier`, mapping userinfo), résolution des 4 routes, `/api/instances/` expose le flag, ruff clean, turbo `check:types` web+space 12/12. ⚠️ **Validation E2E bloquée** tant que la config Supabase (dashboard OAuth Server + client + page `/oauth/consent` externe) n'est pas fournie — cf. `PLAN-SSO-SUPABASE-PLANE.md` §1-3. Hors scope v1 : auto-redirect « sans clic » (§6, touche un fichier core → follow-up).
- **api+web/views-access** — Vues privées/publiques (feature Pro) fonctionnelles en CE — **zéro migration** : le modèle (`IssueView.access` 0=Private/1=Public, défaut Public), le queryset de visibilité (`Q(owned_by)|Q(access=1)`, listes + détail, projet + workspace) et les gardes owner-only existaient déjà ; le POST/PATCH droppait simplement `access` (read_only). Backend : `access` retiré des `read_only_fields` de `IssueViewSerializer` (partagé par les 2 viewsets) — `is_locked` reste non inscriptible ; valeurs hors {0,1} → 400 (ChoiceField dérivé du modèle). Web : stubs CE remplis — sélecteur Public/Private du formulaire de vue (`access-controller.tsx`, réutilise `AccessField` + `VIEW_ACCESS_SPECIFIERS` upstream) et facette de filtre « Access » (`filters/access-filter.tsx`). **Revue adversariale passée** — 3 fuites de vue privée fermées (surfaces devenues significatives dès que `access` est inscriptible) : VA-01 une vue publiée puis passée en privé restait servie via l'anchor public (`space` `AllowAny`) → publication d'une vue privée refusée (400) + dépublication automatique (DeployBoard supprimé) au passage en privé ; VA-02 la recherche globale (`filter_views`) fuitait nom/id des vues privées d'autrui → filtre `Q(access=1)|Q(owned_by=user)` ; VA-03 on pouvait mettre en favori la vue privée d'un autre → vérification d'accès avant création du favori (404 sinon). Deux correctifs UI de la même revue : COR-va-1 le retrait du chip « Private » (valeur 0, falsy) effaçait tout le filtre `view_type` (garde `value === null` au lieu de `!value`) ; COR-va-2 les libellés Private/Public de la facette de filtre passés en clés i18n (`common.access.*` présentes dans les 19 locales) au lieu de littéraux anglais. Vérifié : **16 tests pytest Docker** (persistance access=0, toggle owner, refus non-owner 400/403, access invalide 400, vue privée cachée aux autres / visible au owner — projet ET workspace, is_locked non inscriptible, non-publication + dépublication auto, recherche + favoris scopés), E2E navigateur (création « Vue privee test » → `access:0` persisté, facette rendue), `makemigrations --check` clean, tsc EXIT=0. Hors scope : tier « Shared » (table de partage inexistante), verrouillage de vue, layouts additionnels workspace.
- **api+web/milestones** — Milestones / jalons projet (feature payante) alignés sur le **SDK/MCP officiel** (source de vérité : plane-mcp-server 0.2.19). Modèles greenfield `Milestone(ProjectBaseModel)` (`name`, `description`, `target_date`, `external_source/id`, `sort_order`) table `milestones` et `MilestoneIssue` (FK issue+milestone, `UniqueConstraint(issue, milestone) WHERE deleted_at IS NULL`, calque `CycleIssue`) table `milestone_issues` + toggle `Project.is_milestone_enabled` (défaut False) — **migration `0128_milestones`** (dép 0127). **Couche v1 token/MCP** : CRUD `/api/v1/.../milestones/[{id}/]` + `GET/POST/DELETE .../work-items/` — **`title` exposé via `source="name"`** (le SDK Pydantic l'exige), listes cursor-paginées enveloppe `BasePaginator`, POST work-items `{issues:[uuid]}` AJOUTE (validation UUID + même-projet 400, déjà-liés ignorés), **DELETE avec body** RETIRE (204), dedup import externe 409, écritures gatées 400 si toggle off, scoping ProjectMember. **Couche app interne** pour le web (mêmes règles, retrait unitaire `/milestone-issues/:issue_id/`, rejet explicite des ids étrangers — parité v1). **Web** : type `TMilestone`, `milestone.service.ts`, store MobX scopé projet, page `/:ws/projects/:pid/milestones` + entrée nav gatée, toggle « Milestones » (Project Settings → Features), section liste (modal create/edit RHF, delete + confirmation, attach/detach via `ExistingIssuesListModal`, compteurs X/Y). i18n : 6 clés plates + bloc `disabled_project.empty_state.milestone` × **19 locales** (`sync:check` OK). **Revue adversariale passée** (2 audits sécurité + correction) : sécurité solide (isolation cross-projet/workspace, permissions, gate vérifiés) — 1 durcissement SEC-ms-1 (`MilestoneWriteSerializer` app : `created_by`/`updated_by`/`deleted_at`/`sort_order` passés en `read_only_fields` contre le mass-assignment via PATCH) ; corrections COR-ms-1 (`is_milestone_enabled` ajouté à la projection `.values()` de la liste projets → l'entrée nav sidebar apparaît sans ouvrir le projet), COR-ms-2 (work items du jalon exposés inline via `IssueProjectLiteSerializer` → nom + identifiant `PROJ-N` au lieu de l'UUID brut), COR-ms-3 (liens fetchés à l'ouverture du modal d'attache → items déjà attachés cachés), COR-ms-4 (en-tête de modal + tooltips i18n ×19), COR-ms-5 (lien sidebar ouvert aux guests, parité backend/page). Vérifié : **19 tests pytest Docker** (contrat v1 title/enveloppe/409/gate/work-items ; app CRUD/guest/scoping/compteurs/expansion issue_detail/anti-mass-assignment), `makemigrations --check` clean, turbo `check:types` 11/11, **chaîne MCP validée EN DIRECT sur l'instance locale** (`create_milestone` → `title` ✓, `manage_milestone_work_items` ✓, `list_milestone_work_items` ✓, `update_milestone` ✓, `list_milestones` avec compteurs ✓), UI vérifiée navigateur (page, nav gatée, jalon déplié → work item « DEMO-1 Aider Anthony »).
- **api+web/active-cycles-workspace** — Active Cycles workspace (feature Pro) : page `/:ws/active-cycles` réelle remplaçant le paywall CE. Backend : nouvel endpoint interne `GET /api/workspaces/:slug/active-cycles/` (`WorkspaceActiveCyclesEndpoint`) — cycles actifs (start ≤ now ≤ end, non archivés) des projets où le demandeur est membre actif, avec compteurs de progression (total/completed/started/unstarted/backlog/cancelled), dates en timezone projet, pagination cursor — **zéro migration** (agrégation lecture seule, permission `WorkspaceEntityPermission` + scoping ProjectMember). Web : `ce/components/active-cycles/root.tsx` rempli (grille de cartes cycle : nom, projet, plage de dates, barre de progression + %, compteurs ; loading/erreur/empty state), badge « Pro » neutralisé (header + sidebar). Service front dormant `cycleService.workspaceActiveCycles` réutilisé. i18n : clés existantes uniquement (compteurs des cartes inclus : `workspace_projects.state.*`, `common.pending`, `common.load_more` × 19 locales). Revue adversariale passée (2 auditeurs + double vérif, 6 findings corrigés) : exclusion des projets `cycle_view=False` (COR-4), % de complétion hors cancelled + compteur cancelled affiché — cohérent avec la barre (COR-1), libellés compteurs i18n (COR-2), erreur réseau → état d'erreur et non empty state (COR-3), pagination réelle « Load more » via `useSWRInfinite` (COR-6). Vérifié : **16 tests pytest Docker** (`test_workspace_active_cycles_app.py` : fenêtre active, scoping membre/guest/inactif/étranger, exclusions archivé/draft/soft-delete/cycle_view, compteurs anti-inflation de jointures, pagination/cursor malformé), ruff/py_compile clean, `makemigrations --check` = No changes detected, **E2E live** (cycle actif seedé → `status: CURRENT`, compteurs mis à jour après ajout d'un work item via MCP local), `check:types` EXIT=0, vérif visuelle navigateur.
Expand Down
Loading