# A ticket, and the script that writes them
## The description
```markdown
**Constat** : what happens, where, with what account, since when; the report's numbers (checklist n°36), what was
replayed and how (HTTP 200, `/user/scopes.json` then says `admin: true`).
**Retour Flo** : confirmé <- the reporter's answer, when the report has one
**Attendu** : what "fixed" means, testable (403 for any non admin account, role unchanged, attempt logged).
**Prompt suggéré :**
```claude
<HEAD>
<the bug's own paragraph>
<FOOT>
```
```
The prompt block's language is the agent's (```` ```claude ````, ```` ```chatgpt ````, ```` ```ai ```` for any:
mmo.client's `KanbanPrompts` reads them); `api/boards/{board}/prompts.json?agent=claude` lists them.
## The head and the foot (my.oviepro, in French as its report was)
```text
HEAD = Contexte : application my.oviepro (`~/dev/src/my.oviepro`, oviepro 4.1.0, branche develop), servie en local
sur http://localhost/my.oviepro/ ; les bibliothèques MMO sont dans `~/dev/src/MMO/master/libs` (mmo.core,
mmo.client, mmo.admin, mmo.oauth, mmo.edms, mmo.pdf...). Bug relevé en recette de la release sur dev.oviepro.com.
FOOT = Reproduis d'abord le problème en local (skill `mmo-agent-login` pour te connecter, avec un compte sans droit
d'administration quand c'est pertinent), corrige-le à sa cause plutôt qu'en surface, puis vérifie à l'écran ou par
l'API. Si la cause est dans une bibliothèque MMO, corrige-la dans la bibliothèque et dis-le. Ne commite pas :
résume ce que tu as changé (fichiers, raison, comment vérifier) dans la discussion du ticket.
```
A ticket found while fixing others says so in its head: "Problème découvert pendant la correction des tickets de
recette du 05/10/2026" and its constat names the ticket that found it ("Trouvé par l'agent du ticket #62").
## The bug's paragraph
- What happens, with the route, file or class it happens in: `PUT /data/main/roles.json` (route
`PUT|data/{repository}/{collection}` of `libs/core/src/controllers/RepositoryController.php`).
- Where to look, when you know: `src/views/parts/temoins-tiers.jhs`, the `typeTemoin` property of
`src/entities/Accident.php`.
- What to do, and its limits: "rends le type obligatoire dès qu'un témoin est saisi (validation de l'entité et de
l'étape)", "propose un script de reprise, sans le lancer", "n'applique rien en base sans validation".
- What to prove: "ajoute un test qui rejoue l'attaque", "vérifie aussi POST/DELETE et les écritures en lot",
"liste dans ton compte rendu chaque page vérifiée".
- A decision that is not the agent's is asked for, not taken: "explique ... et si l'échec du mail doit vraiment
empêcher le changement de statut".
## The generator
```python
#builds the {board} board's backlog from "{report}"
import json, urllib.request
API = 'http://localhost/my.oviepro/api/boards/{board}'
HEAD = ('Contexte : ...\n\n')
FOOT = ('\n\nReproduis d\'abord le problème en local ... dans la discussion du ticket.')
board = json.load(urllib.request.urlopen(API + '.json'))['data']
data = board['data']
first = 1 + max([int(t['id']) for t in data.get('tickets', []) if str(t['id']).isdigit()] or [0])
tickets = []
def T(title, category, labels, constat, prompt, attendu=None, retour=None):
d = '**Constat** : ' + constat
if retour: d += '\n\n**Retour Flo** : ' + retour
if attendu: d += '\n\n**Attendu** : ' + attendu
d += '\n\n**Prompt suggéré :**\n\n```claude\n' + HEAD + prompt + FOOT + '\n```\n'
tickets.append({'id': str(first + len(tickets)), 'title': title, 'status': 'backlog', 'labels': labels,
'category': category, 'description': d, 'parent': None, 'constraint': None})
T('Escalade de privilèges : un compte non admin se donne admin: true par PUT /data/main/roles.json', 'securite',
['bloquant', 'securite', 'confirme-flo'],
'un compte sans droit d\'administration modifie son propre rôle ...',
'Un compte sans droit d\'administration peut modifier son propre rôle par `PUT /data/main/roles.json` ...',
attendu='403 pour tout compte non admin, rôle inchangé, tentative tracée.', retour='confirmé')
# ... one T() per bug
data['tickets'] = data.get('tickets', []) + tickets
#labels and categories the tickets use must exist on the board: add the missing ones to data['labels'] / data['categories']
req = urllib.request.Request(API + '.json', method='PUT', headers={'Content-Type': 'application/json'},
data=json.dumps({'version': board['version'], 'data': data}).encode())
print(json.load(urllib.request.urlopen(req))['data']['version'], len(tickets), 'tickets')
```
A label is `{"id": "bloquant", "label": "bloquant", "color": "red"}` (`icon` optional, a Font Awesome name that
exists: check it); a category the same; a status `{"id": "todo", "label": "À faire", "type": "backlog"}`, its type
one of `backlog`, `active`, `done`.