---
suivi: 1346
date: 2026-08-14
sujet: diag_sharepoint_403_nommage
chantier: sharepoint_ged
type: diagnostic
statut: poussé
hash: 74518828
fichiers:
  - tools/diag/diag_sharepoint_403_nommage_1346.php
  - docs/suivi/SUIVI_1346_diag_sharepoint_403_nommage.md
---

## PROMPT ENVOYÉ

SUIVI #1346 PHASE 1 — diagnostic seul. 403 accessDenied à la création de
dossiers SharePoint (marchés 186, 188, 189). Hypothèse nom/chemin long.
Lecture seule. Pas de fix, pas de migration, pas de touche SharePointService.

## SYNTHÈSE

### Fiches lues
- `#1231` LEÇON : `normalizeMarcheSegment` → `normalizeDynamicFolderName` sur
  le chemin VEFA `{POSTE}_{FOURNISSEUR}` ; pas de troncature ; dossiers
  existants non renommés.
- `#1230` : arborescence ; CLI Graph historiquement fragile — le prompt
  actuel autorise HTTPS CLI hors `*.powerplatform.com`.
- `#1098` : lotissement `TRAVAUX/4_PRESTATAIRES` ; collision suffixe `_{id}`.
- `#1063` : sync programme = token **app** ; création dossier marché = token
  **délégué**. `#1338/#1340` PDF SIWI — non concernés.

### OBJET 1 — chemin complet
Formule VEFA (ENVOL, log programme_id=10) :

`{DEPT}/{COMMUNE}/{code_envol}--{LIBELLE}/05_SUIVI_CHANTIER/4_PRESTATAIRES/{POSTE}_{FOURNISSEUR}`

Feuille (logs, mesurée) :
- 186 / 188 : `GROUPEMENT_ARCHITECTE_MOE_BET_ARCHIGROUP` **40** caractères
- 189 : `BUREAU_DE_CONTROLE_-_LP_SH_PHH_TH_HAND_NF_BUREAU_ALPES_CONTROLES` **64**

Les deux sont << 255 (max segment SharePoint). Si le 403 masque une limite
de chemin (~400), c’est le **préfixe programme** qu’il faut mesurer, pas la
feuille. Longueurs `sharepoint_folder_url` + branche + feuille = sortie OVH
du script (DB + GET Graph lecture seule). Cursor n’a pas la base prod.

Cas qui fonctionne : programme 17 (PUISSERG00 / HEC34-003) — enseigne
HECTARE donc `TRAVAUX/4_PRESTATAIRES` (pas `05_SUIVI_CHANTIER`). Comparer
`after_sites_len` des deux parents dans la sortie OVH.

### OBJET 2 — charset
Interdits Microsoft (`~ # % & * { } \ : < > ? / | "` + espace/point en
bord) : **AUCUN** dans les 3 noms **envoyés** à Graph.

| Nom | Alphabet | Motif `_-_` |
|---|---|---|
| 186/188 | A–Z `_` uniquement | non |
| 189 | A–Z `_` `-` | oui (`CONTROLE_-_LP`) |

`normalizeDynamicFolderName` **strip** `|` (preuve runtime :
`POSTE | AVEC | PIPES` → `POSTE_AVEC_PIPES`). Un `|` dans le libellé
d’origine n’arrive pas jusqu’à l’API.

Le charset **seul n’explique pas** 186/188 (nom « propre »). 189 a le
motif `_-_` issu d’un « - » source (espace-tiret-espace → `_` `-` `_`,
non collapse car `-` casse le `_+`).

### OBJET 3 — 403 = création feuille
Séquence logs = `ensureFolder` POST `/items/{parentId}/children` avec
`name` = feuille, `conflictBehavior=fail`, **skipCheck=true**.
- Parent manquant → log `05_SUIVI_CHANTIER introuvable` ou `dossier de tête`
  `folder=4_PRESTATAIRES`. **Absent** des extraits.
- Création feuille → `échec création dossier` `name=GROUPEMENT…` `status=403`.
  **Présent**.
- Droit biblio global → les dépôts et le sync 17 ne passeraient pas.

Token : marché = `getAccessToken()` (délégué). Sync 17 =
`getAppAccessToken()`. Un 403 **nom-spécifique** n’est pas une panne
d’auth globale.

### OBJET 4 — normaliseur traversé
`creerDossierMarcheVefa` appelle `normalizeMarcheSegment` ×2 (poste,
fournisseur). Délègue `normalizeDynamicFolderName`. **Pas**
`sanitizeSharePointFolderSegment` (troncature 80) sur ce chemin.
**Pas de troncature** du tout.

Preuve runtime : `normalize(GROUPEMENT ARCHITECTE MOE BET)_normalize(ARCHIGROUP)`
= `GROUPEMENT_ARCHITECTE_MOE_BET_ARCHIGROUP` **égal au log**.

### OBJET 5 — lot F (requête CSV prod, 1128 créables C+G)
`docs_creable_C_G_csv=2817` (cible énoncée).

| Signature | n |
|---|---|
| marchés créables | 1128 |
| len ≥ 40 (comme 186) | 457 |
| len ≥ 50 | 198 |
| len ≥ 60 (voisin 189=64) | 81 |
| len ≥ 80 | 9 |
| hyphen restant | 271 |
| motif `_-_` (comme 189) | **204** |
| `|` source | 0 |
| autre interdit source (stripés) | 81 |
| max | 96 (`ERDF_HT_…_ROUSSILLON`) |

204 dossiers futurs ont **déjà** le motif de 189. 457 ont une feuille
aussi longue que 186. Aucun > 255.

### OBJET 6 — options (pas de choix)
A) Normaliser `-` → `_` / collapse `_-_`. Lookup **nouveau puis legacy**.
   Orphelin si on n’essaie pas l’ancien nom.
B) Troncature (ex. 60) + `_{marche.id}` si collision ou 403. Existants
   inchangés ; resolve long puis tronqué.
C) Raccourcir le chemin (code poste `C02` pas libellé). Convention
   nouvelle ; existants orphelins sans double résolution.

Ne pas renommer la prod sans plan de reprise.

## DÉPLOIEMENT-TEST

```bash
cd /home/hectare-envol/envol.hectare.fr && php -d display_errors=1 tools/diag/diag_sharepoint_403_nommage_1346.php
```

Coller OBJET 1 (len after_sites 10 vs 17, egal_log, GET status). Pas de migrate.
Pas de journal:sync (aucun impact utilisateur).

## LEÇON

Un 403 `accessDenied` à la création Graph peut masquer un refus de **nom
ou de chemin** ; le `name` du log est déjà le normaliseur (pas le libellé
brut). `|` source est stripé. ` - ` source devient `_-_` et n’est pas
collapse. 186/188 n’ont aucun caractère interdit : mesurer le chemin
**complet** (URL parent + feuille) avant d’incriminer le charset.
Le normaliseur VEFA est bien traversé et **ne tronque pas**.
