doc: split Architecture.md into per-topic pages

doc/Architecture.md was 436 lines and growing; this commit
extracts each non-trivial subject into its own page under
doc/architecture/ and reduces the root document to a table of
contents + transversal sections (vision, stack, admin rights).

New pages (under doc/architecture/):

- workflow-multi-parties.md : client / fournisseur /
  coordinateur roles, sous-traitance, project states, B2B/B2C,
  domaine musical production flow.
- domaine-musical.md : titres collaboratifs (formats, flux de
  production, contraintes de licence).
- licences.md : LicenceModele, CC/ODbL seed, badge projet,
  cycle de vie.
- domaines-activite.md : arbre des activites, Droit a la
  racine, DomaineActivite model.
- dictionnaires-metier.md : regle d'heritage, DictionnaireMetier
  + TermeMetier, cycle de vie d'un terme. Absorbs the previous
  doc/Dictionnaire.md draft.
- offres-frontmatter.md : ClasseFormulaire / ClasseDevis,
  OffreFournisseur, Demande, parsing YamlDotNet (introduit dans
  4034c399 Front matters). Absorbs the previous
  doc/Formulaires-devis.md and doc/Demande.md fragments.
- postit-oidc.md : documentation du client desktop PostIt,
  custom URI scheme (RFC 8252 §7.1), composants partages,
  plateformes, UX observable, persistance et reprise au boot,
  garanties testees.

Architecture.md (436 -> 66 lines) keeps the vision, the stack
overview, the admin rights section, and a TOC table pointing at
each detail page. The "A documenter ensuite" backlog is kept
at the end.

Cross-links are relative: from Architecture.md the links go
architecture/<page>.md; from inside doc/architecture/ they go
../Architecture.md or <sibling>.md.
This commit is contained in:
Paul Schneider 2026-06-27 13:45:31 +01:00
commit 74de6aa1d1
11 changed files with 548 additions and 338 deletions

View file

@ -9,103 +9,20 @@ Elle gère des **devis** (pas de facturation directe), et s'intègre
dans des workflows collaboratifs pouvant aboutir à des livrables dans des workflows collaboratifs pouvant aboutir à des livrables
sous licence libre. sous licence libre.
--- ## Pages de détail
## Workflow de mise en relation multi-parties Chaque sujet est détaillé dans sa propre page (sous
`doc/architecture/`) ; cette racine tient lieu de sommaire.
### Initiateur | Sujet | Page |
|------------------------------------------------|-------------------------------------------------------------------|
Le projet peut être initié par n'importe quelle partie : | Workflow multi-parties (client / fournisseur / coordinateur, sous-traitance, états) | [workflow-multi-parties.md](architecture/workflow-multi-parties.md) |
- Un **client** (particulier ou pro) qui exprime un besoin | Domaine musical (titres collaboratifs) | [domaine-musical.md](architecture/domaine-musical.md) |
- Un **fournisseur** qui propose une offre ou monte un collectif | Licences (`LicenceModele`, seed CC/ODbL, badge)| [licences.md](architecture/licences.md) |
- Un **tiers coordinateur** qui orchestre sans être client ni prestataire | Domaines d'activité (arbre, `DomaineActivite`) | [domaines-activite.md](architecture/domaines-activite.md) |
| Dictionnaires métier (héritage en arbre) | [dictionnaires-metier.md](architecture/dictionnaires-metier.md) |
### Rôles | Offre fournisseur + frontmatter (`ClasseFormulaire`, `ClasseDevis`, parsing) | [offres-frontmatter.md](architecture/offres-frontmatter.md) |
| PostIt / OIDC (client desktop, custom URI scheme, silent refresh) | [postit-oidc.md](architecture/postit-oidc.md) |
| Rôle | Type de compte | Description |
|---------------|--------------------|-----------------------------------------------------------|
| Client | Pro ou particulier | Exprime le besoin, valide les devis, co-signe la licence |
| Fournisseur | Pro | Répond aux besoins, peut sous-traiter |
| Coordinateur | Pro ou particulier | Orchestre le projet sans relation de facturation directe |
### Sous-traitance
Un fournisseur peut faire appel à d'autres fournisseurs,
**avec accord explicite du client**. Chaque sous-traitant :
- Est visible dans le projet
- Co-signe l'accord de licence
- Peut recevoir un devis distinct
### B2B / B2C
La distinction est portée par le **type de compte** :
- Compte **pro** : SIRET, TVA, facturation professionnelle
- Compte **particulier** : usage personnel, sans obligations fiscales pro
Un projet peut mélanger les deux (ex: un particulier client,
plusieurs prestataires pro) — Yavsc n'impose pas d'homogénéité.
### États d'un projet
```
Initié → En recherche de parties → Devis en cours
→ Accord de licence signé → En production
→ Livré → Publié (si licence libre)
```
### Contrainte clé
L'accord de licence est établi **avant** tout début de production,
signé (ou validé) par toutes les parties : client, fournisseurs,
et sous-traitants éventuels.
---
## Domaine musical — Titres collaboratifs
### Objectif
Permettre la production collaborative de titres musicaux sous licence
libre, à partir de la mise en relation assurée par Yavsc.
### Formats supportés
- Audio (ex: WAV, FLAC, MP3)
- Partition (ex: MusicXML, LilyPond, PDF)
- MIDI
### Flux de production
1. Un client exprime un besoin musical
2. Des prestataires répondent avec des devis
3. Un **consensus est établi en amont** entre le client et les
contributeurs sur la licence du livrable final
4. La collaboration produit les fichiers
5. Le titre est publié sous la licence choisie
---
## Licences
### Modèle `LicenceModele`
Géré par l'administration Yavsc. Chaque modèle porte :
- `EstLibre` (bool) — détermine le badge affiché sur le projet
- Les conditions : attribution, partage à l'identique, usage commercial,
modification
- Une URL vers le texte officiel de la licence
### Seed initial
Licences Creative Commons préchargées : CC0, CC BY, CC BY-SA, CC BY-ND,
CC BY-NC, CC BY-NC-SA, CC BY-NC-ND (toutes en version 4.0) + ODbL 1.0.
### Badge projet
Rendu CSS/HTML — vert si `EstLibre`, orange sinon.
---
## Stack technique ## Stack technique
@ -114,14 +31,15 @@ Rendu CSS/HTML — vert si `EstLibre`, orange sinon.
- **Base de données** : PostgreSQL (provider Npgsql) - **Base de données** : PostgreSQL (provider Npgsql)
- **Frontend** : Razor views - **Frontend** : Razor views
- **Parsing frontmatter** : YamlDotNet - **Parsing frontmatter** : YamlDotNet
- **Client desktop** : Avalonia 12 (PostIt — détails dans
--- [postit-oidc.md](architecture/postit-oidc.md))
## Droits de Yavsc ## Droits de Yavsc
### Administration ### Administration
Le groupe des administrateurs prend la charge de : Le groupe des administrateurs prend la charge de :
- la Gestion des licences - la Gestion des licences
- la Gestion des groupes d'utilisateurs (les modérateurs, en particulier) - la Gestion des groupes d'utilisateurs (les modérateurs, en particulier)
- la Gestion des projets - la Gestion des projets
@ -129,8 +47,9 @@ Le groupe des administrateurs prend la charge de :
### Gestion des utilisateurs et des groupes ### Gestion des utilisateurs et des groupes
En supposant que les certificats de letsencrypt sont au groupe `www-data`, En supposant que les certificats de letsencrypt sont au groupe
on peut créer l'utilisateur `yavsc` avec les droits suivants : `www-data`, on peut créer l'utilisateur `yavsc` avec les droits
suivants :
```bash ```bash
sudo addgroup yavsc --system sudo addgroup yavsc --system
@ -138,169 +57,6 @@ sudo adduser --ingroup yavsc --add-extra-groups www-data \
--disabled-password --system yavsc --disabled-password --system yavsc
``` ```
---
## Domaines d'activité
### Hiérarchie
Les activités sont organisées en arbre. Chaque activité peut avoir
une activité parente d'ordre plus général :
```
Droit ← racine, ancêtre commun
├── Musique
│ ├── Jazz
│ ├── Classique
│ └── ...
├── Graphisme
├── BTP
└── ...
```
Le domaine **Droit** est positionné à la racine — *nul n'est censé
ignorer la loi*. Sa position structurelle rend ses dictionnaires
naturellement disponibles dans tous les projets, sans attribut spécial.
### Modèle `DomaineActivite`
```
DomaineActivite
- Id, Nom
- ParentId (FK → DomaineActivite, nullable)
```
Pas de booléen `EstTransversal` — la transversalité est une conséquence
de la position dans l'arbre, pas un attribut explicite.
---
## Dictionnaires métier
### Principe
Les dictionnaires sont des **modèles publics** de termes métier,
maintenus par les modérateurs et alimentés par les fournisseurs.
Au moment de créer un projet, le dictionnaire est **copié**
c'est un instantané, pas une référence vivante.
### Règle de résolution
> Un projet hérite des dictionnaires de son activité **et de tous
> ses ancêtres** jusqu'à la racine.
Ainsi, les termes juridiques sont toujours disponibles, quel que
soit le domaine du projet.
### Modèle
```
DictionnaireMetier
- Id, Nom
- DomaineActiviteId (FK)
- Langue ← attribut du dictionnaire entier
TermeMetier
- Id
- DictionnaireMetierId (FK)
- Mot
- Definition
- StatutValidation ← Proposé | Validé | Rejeté
- ProposeParId (FK → ApplicationUser)
- ValidéParId (FK → ApplicationUser, nullable)
```
### Cycle de vie d'un terme
1. Un **fournisseur** propose un terme dans son domaine et sa langue
2. Un **modérateur** valide → le terme intègre le dictionnaire public
3. À la création d'un projet, le dictionnaire est **associé par copie**
---
## Offre fournisseur et modèle canonique de demande
### Principe
Un fournisseur décrit son offre en **Markdown**, avec un en-tête
structuré délimité par `---` (frontmatter YAML standard) qui porte
les métadonnées de formulaire et de devis. Le corps est libre.
```markdown
---
formulaire: Prestation Musicale
devis: Arrangement Orchestral
---
Je propose des arrangements pour orchestre de chambre,
livraison sous 3 semaines, formats MusicXML et PDF...
```
### `ClasseFormulaire` et `ClasseDevis`
Ces deux référentiels sont **définis côté serveur par les modérateurs**,
pour servir de modèles de formulaires et de devis. Les fournisseurs
s'y conforment — ils ne peuvent pas en créer de nouveaux.
### Modèle canonique nom/valeur
```
ClasseFormulaire
- Id, Nom
- DomaineActiviteId (FK)
└── ChampDemande
- Nom (ex: "tempo", "tonalité", "durée")
- TypeValeur (texte | entier | décimal | booléen | enum)
- Obligatoire (bool)
- ValeurDefaut
ClasseDevis
- Id, Nom
- DomaineActiviteId (FK)
OffreFournisseur
- Id
- FournisseurId (FK → ApplicationUser)
- DomaineActiviteId (FK)
- ContenuMarkdown ← corps libre de l'offre
- ClasseFormulaireId (FK) ← extrait du frontmatter à la sauvegarde
- ClasseDevisId (FK) ← extrait du frontmatter à la sauvegarde
```
### Demande client
À partir de l'offre, la demande instancie les champs du formulaire :
```
Demande
- Id
- OffreFournisseurId (FK)
- ClientId (FK → ApplicationUser)
└── ValeurChampDemande
- ChampDemandeId (FK)
- Valeur (string — sérialisé selon TypeValeur)
```
### Règle d'héritage
Les `ClasseFormulaire` et `ClasseDevis` disponibles pour une activité
incluent ceux de ses **ancêtres** dans l'arbre — cohérent avec la
règle de résolution des dictionnaires.
### Parsing du frontmatter avec YamlDotNet
```csharp
var parts = markdown.TrimStart()
.Substring(3)
.Split(new[] { "\n---" }, 2,
StringSplitOptions.None);
var yamlBlock = parts[0].Trim();
var corps = parts[1].Trim();
var meta = deserializer.Deserialize<FrontmatterOffreResult>(yamlBlock);
```
---
## À documenter ensuite ## À documenter ensuite
- Modèle `ProjetMusical` - Modèle `ProjetMusical`

View file

@ -1,7 +0,0 @@
Demande
- Id
- OffreFournisseurId (FK)
- ClientId (FK → ApplicationUser)
└── ValeurChampDemande
- ChampDemandeId (FK)
- Valeur (string — sérialisé selon TypeValeur)

View file

@ -1,36 +0,0 @@
# Dictionnaires métier
## Principe
Les dictionnaires sont des modèles publics de termes métier,
maintenus par les modérateurs et alimentés par les fournisseurs.
Au moment de créer un projet, le dictionnaire est copié —
c'est un instantané, pas une référence vivante.
## Règle de résolution
Un projet hérite des dictionnaires de son activité et de tous
ses ancêtres jusqu'à la racine.
Ainsi, les termes juridiques sont toujours disponibles, quel que
soit le domaine du projet.
Modèle
Le modèle de dictionnaire du contrat est le dictionnaire défini par l'activité ciblée par le client.
## Dictionnaires juridiques
Les dictionnaires juridiques sont des modèles publics de termes juridiques,
maintenus par les modérateurs et alimentés par les modérateurs.
## Règle de résolution
Un projet hérite des dictionnaires de son activité et de tous
ses ancêtres jusqu à la racine.
Ainsi, les termes juridiques sont toujours disponibles, quel que
soit le domaine du projet.
Ce dictionnaire juridique est simplement fondamental, par construction, c'est la base de tous les dictionnaires.

View file

@ -1,33 +0,0 @@
ClasseDevis
- Id, Nom
- DomaineActiviteId (FK)
OffreFournisseur
- Id
- FournisseurId (FK → ApplicationUser)
- DomaineActiviteId (FK)
- ContenuMarkdown ← corps libre de l'offre
- ClasseFormulaireId (FK) ← extrait du frontmatter à la sauvegarde
- ClasseDevisId (FK) ← extrait du frontmatter à la sauvegarde
ClasseFormulaire
- Id, Nom
- DomaineActiviteId (FK)
└── ChampDemande
- Nom (ex: "tempo", "tonalité", "durée")
- TypeValeur (texte | entier | décimal | booléen | enum)
- Obligatoire (bool)
- ValeurDefaut
ClasseDevis
- Id, Nom
- DomaineActiviteId (FK)
OffreFournisseur
- Id
- FournisseurId (FK → ApplicationUser)
- DomaineActiviteId (FK)
- ContenuMarkdown ← corps libre de l'offre
- ClasseFormulaireId (FK) ← extrait du frontmatter à la sauvegarde
- ClasseDevisId (FK) ← extrait du frontmatter à la sauvegarde

View file

@ -0,0 +1,68 @@
# Dictionnaires métier
> **Récapitulatif** : Les dictionnaires sont des modèles publics
> de termes métier, validés par les modérateurs et **copiés**
> dans chaque projet (instantané, pas référence vivante).
> L'héritage suit l'arbre des activités. Détail dans cette page,
> racine de l'architecture : [Architecture.md](../Architecture.md).
## Principe
Les dictionnaires sont des **modèles publics** de termes métier,
maintenus par les modérateurs et alimentés par les fournisseurs.
Au moment de créer un projet, le dictionnaire est **copié**
c'est un instantané, pas une référence vivante.
> Note historique : une première ébauche de cette page existait
> à `doc/Dictionnaire.md`. Elle a été absorbée ici.
## Règle de résolution
> Un projet hérite des dictionnaires de son activité **et de tous
> ses ancêtres** jusqu'à la racine.
Ainsi, les termes juridiques sont toujours disponibles, quel que
soit le domaine du projet.
Le modèle de dictionnaire du contrat est le dictionnaire défini
par l'activité ciblée par le client.
## Dictionnaires juridiques
Les dictionnaires juridiques sont des modèles publics de termes
juridiques, maintenus et alimentés par les modérateurs. Ils
figurent à la racine de l'arbre (sous `Droit`) et profitent donc
à toutes les activités par construction.
> Ce dictionnaire juridique est simplement fondamental, par
> construction, c'est la base de tous les dictionnaires.
## Modèle
```
DictionnaireMetier
- Id, Nom
- DomaineActiviteId (FK)
- Langue ← attribut du dictionnaire entier
TermeMetier
- Id
- DictionnaireMetierId (FK)
- Mot
- Definition
- StatutValidation ← Proposé | Validé | Rejeté
- ProposeParId (FK → ApplicationUser)
- ValidéParId (FK → ApplicationUser, nullable)
```
## Cycle de vie d'un terme
1. Un **fournisseur** propose un terme dans son domaine et sa langue
2. Un **modérateur** valide → le terme intègre le dictionnaire public
3. À la création d'un projet, le dictionnaire est **associé par copie**
## Voir aussi
- [Architecture.md](../Architecture.md) — racine.
- [domaines-activite.md](domaines-activite.md) — l'arbre des
activités qui justifie la règle d'héritage.

View file

@ -0,0 +1,43 @@
# Domaine musical — Titres collaboratifs
> **Récapitulatif** : Yavsc permet de produire collaborativement
> des titres musicaux sous licence libre, en s'appuyant sur la
> mise en relation multi-parties. Détail dans cette page, racine
> de l'architecture : [Architecture.md](../Architecture.md).
## Objectif
Permettre la production collaborative de titres musicaux sous
licence libre, à partir de la mise en relation assurée par Yavsc.
## Formats supportés
- Audio (ex : WAV, FLAC, MP3)
- Partition (ex : MusicXML, LilyPond, PDF)
- MIDI
## Flux de production
1. Un client exprime un besoin musical
2. Des prestataires répondent avec des devis
3. Un **consensus est établi en amont** entre le client et les
contributeurs sur la licence du livrable final
4. La collaboration produit les fichiers
5. Le titre est publié sous la licence choisie
## Contraintes spécifiques
- L'accord de licence sur le livrable musical est une instance
du modèle `LicenceModele` détaillé dans
[licences.md](licences.md).
- L'arborescence d'activités musicales (`Musique → Jazz`,
`Musique → Classique`, etc.) est décrite dans
[domaines-activite.md](domaines-activite.md).
## Voir aussi
- [Architecture.md](../Architecture.md) — racine.
- [workflow-multi-parties.md](workflow-multi-parties.md) — rôle
client/fournisseur/coordinateur dans la production musicale.
- [licences.md](licences.md) — modèle de licence applicable au
livrable.

View file

@ -0,0 +1,59 @@
# Domaines d'activité
> **Récapitulatif** : Yavsc organise ses activités en arbre, avec
> `Droit` à la racine pour rendre les termes juridiques
> disponibles partout. Pas de booléen "transversal" : la
> transversalité découle de la position dans l'arbre. Détail dans
> cette page, racine de l'architecture : [Architecture.md](../Architecture.md).
## Hiérarchie
Les activités sont organisées en arbre. Chaque activité peut avoir
une activité parente d'ordre plus général :
```
Droit ← racine, ancêtre commun
├── Musique
│ ├── Jazz
│ ├── Classique
│ └── ...
├── Graphisme
├── BTP
└── ...
```
Le domaine **Droit** est positionné à la racine — *nul n'est
censé ignorer la loi*. Sa position structurelle rend ses
dictionnaires naturellement disponibles dans tous les projets,
sans attribut spécial.
## Modèle `DomaineActivite`
```
DomaineActivite
- Id, Nom
- ParentId (FK → DomaineActivite, nullable)
```
Pas de booléen `EstTransversal` — la transversalité est une
conséquence de la position dans l'arbre, pas un attribut
explicite.
## Conséquences sur les autres référentiels
Cette arborescence irrigue :
- les **dictionnaires métier** (cf. [dictionnaires-metier.md](dictionnaires-metier.md)) :
un projet hérite des dictionnaires de ses ancêtres.
- les **classes de formulaire et de devis** (cf.
[offres-frontmatter.md](offres-frontmatter.md)) : un fournisseur
rattaché à `Musique → Jazz` a accès aux modèles de son
activité et de ses ancêtres.
## Voir aussi
- [Architecture.md](../Architecture.md) — racine.
- [dictionnaires-metier.md](dictionnaires-metier.md) — règle
d'héritage en arbre.
- [offres-frontmatter.md](offres-frontmatter.md) — modèle
canonique nom/valeur pour formulaires et devis.

View file

@ -0,0 +1,42 @@
# Licences
> **Récapitulatif** : Yavsc référence des modèles de licence
> (Creative Commons + ODbL) maintenus par l'administration. Chaque
> projet signe l'accord avant toute production. Détail dans cette
> page, racine de l'architecture : [Architecture.md](../Architecture.md).
## Modèle `LicenceModele`
Géré par l'administration Yavsc. Chaque modèle porte :
- `EstLibre` (bool) — détermine le badge affiché sur le projet
- Les conditions : attribution, partage à l'identique, usage
commercial, modification
- Une URL vers le texte officiel de la licence
## Seed initial
Licences Creative Commons préchargées : CC0, CC BY, CC BY-SA,
CC BY-ND, CC BY-NC, CC BY-NC-SA, CC BY-NC-ND (toutes en version
4.0) + ODbL 1.0.
## Badge projet
Rendu CSS/HTML — vert si `EstLibre`, orange sinon.
## Cycle de vie
1. L'administration crée ou met à jour un `LicenceModele`.
2. À la création d'un projet, le client choisit un modèle
dans la liste des licences applicables à son activité.
3. Le projet passe par l'état **Devis en cours** avant que
l'accord ne soit signé par toutes les parties (cf.
[workflow-multi-parties.md](workflow-multi-parties.md)).
4. Une fois signé, l'état passe à **En production** ; le badge
du projet reflète `EstLibre`.
## Voir aussi
- [Architecture.md](../Architecture.md) — racine.
- [workflow-multi-parties.md](workflow-multi-parties.md) —
contrainte clé : accord signé avant production.

View file

@ -0,0 +1,106 @@
# Offre fournisseur, formulaire, devis
> **Récapitulatif** : Le fournisseur rédige son offre en Markdown
> avec un en-tête frontmatter YAML qui pointe vers un
> `ClasseFormulaire` et un `ClasseDevis` définis par les
> modérateurs. La demande du client instancie les champs du
> formulaire. L'héritage suit l'arbre des activités. Détail dans
> cette page, racine de l'architecture : [Architecture.md](../Architecture.md).
> Note historique : des fragments existaient à `doc/Formulaires-devis.md`
> et `doc/Demande.md`. Ils sont absorbés ici.
## Principe
Un fournisseur décrit son offre en **Markdown**, avec un en-tête
structuré délimité par `---` (frontmatter YAML standard) qui porte
les métadonnées de formulaire et de devis. Le corps est libre.
```markdown
---
formulaire: Prestation Musicale
devis: Arrangement Orchestral
---
Je propose des arrangements pour orchestre de chambre,
livraison sous 3 semaines, formats MusicXML et PDF...
```
## `ClasseFormulaire` et `ClasseDevis`
Ces deux référentiels sont **définis côté serveur par les
modérateurs**, pour servir de modèles de formulaires et de devis.
Les fournisseurs s'y conforment — ils ne peuvent pas en créer
de nouveaux.
## Modèle canonique nom/valeur
```
ClasseFormulaire
- Id, Nom
- DomaineActiviteId (FK)
└── ChampDemande
- Nom (ex : "tempo", "tonalité", "durée")
- TypeValeur (texte | entier | décimal | booléen | enum)
- Obligatoire (bool)
- ValeurDefaut
ClasseDevis
- Id, Nom
- DomaineActiviteId (FK)
OffreFournisseur
- Id
- FournisseurId (FK → ApplicationUser)
- DomaineActiviteId (FK)
- ContenuMarkdown ← corps libre de l'offre
- ClasseFormulaireId (FK) ← extrait du frontmatter à la sauvegarde
- ClasseDevisId (FK) ← extrait du frontmatter à la sauvegarde
```
## Demande client
À partir de l'offre, la demande instancie les champs du formulaire :
```
Demande
- Id
- OffreFournisseurId (FK)
- ClientId (FK → ApplicationUser)
└── ValeurChampDemande
- ChampDemandeId (FK)
- Valeur (string — sérialisé selon TypeValeur)
```
## Règle d'héritage
Les `ClasseFormulaire` et `ClasseDevis` disponibles pour une
activité incluent ceux de ses **ancêtres** dans l'arbre —
cohérent avec la règle de résolution des dictionnaires
([dictionnaires-metier.md](dictionnaires-metier.md)).
## Parsing du frontmatter avec YamlDotNet
Le parsing du frontmatter vit dans
`src/Yavsc.Server/Services/FrontmatterParser.cs` (introduit dans
le commit `4034c399 Front matters`). Squelette :
```csharp
var parts = markdown.TrimStart()
.Substring(3)
.Split(new[] { "\n---" }, 2,
StringSplitOptions.None);
var yamlBlock = parts[0].Trim();
var corps = parts[1].Trim();
var meta = deserializer.Deserialize<FrontmatterOffreResult>(yamlBlock);
```
Le résultat `FrontmatterOffreResult` est sérialisé en JSON pour
alimenter `OffreFournisseur.ClasseFormulaireId` /
`ClasseDevisId` à la sauvegarde.
## Voir aussi
- [Architecture.md](../Architecture.md) — racine.
- [domaines-activite.md](domaines-activite.md) — arbre des
activités et héritage.

View file

@ -0,0 +1,133 @@
# PostIt — Client desktop et authentification OIDC
> **Récapitulatif** : PostIt est un client desktop Avalonia 12
> multi-cible (Desktop, Android, Browser/WASM) qui consomme l'API
> Yavsc.Org. Authentification **public client + PKCE** via
> **custom URI scheme** (RFC 8252 §7.1) — pas de loopback HTTP.
> Détail dans cette page, racine de l'architecture : [Architecture.md](../Architecture.md).
## Rôle
PostIt est un client desktop (Avalonia 12, multi-cible : Desktop
Linux/Windows, Android, Browser/WASM) qui consomme l'API
Yavsc.Org pour les opérations CRUD du blog et d'autres
ressources de l'utilisateur. C'est un **public client** : pas de
secret, le secret est remplacé par PKCE.
## Flow d'authentification
L'authentification suit **RFC 8252 §7.1** (OAuth 2.0 for Native
Apps — Custom URI Scheme Redirect), pas le pattern loopback
historique. La séquence :
1. L'utilisateur clique **Se connecter** sur `LoginPage`.
2. `YavscApiClient.LoginInteractiveAsync` demande à `OidcClient`
de calculer l'authorize URL et de la passer à
`CustomSchemeBrowser.InvokeAsync`.
3. `CustomSchemeBrowser` ouvre le navigateur système sur l'authorize
URL via `Process.Start(options.StartUrl) { UseShellExecute = true }`,
puis **attend** sur un `TaskCompletionSource<string>` alimenté
par un named pipe `PostIt.OidcCallback`.
4. L'utilisateur s'authentifie sur l'IdP. Le navigateur redirige
vers `postit://callback?code=***&state=…`.
5. L'OS, qui a le scheme `postit://` enregistré, **lance une 2ᵉ
instance** de PostIt avec l'URL en argument.
6. La 2ᵉ instance lit `Environment.GetCommandLineArgs()` *avant*
qu'Avalonia ne boote (`PostIt.Desktop.Program.Main`),
détecte l'URL via `SchemeUrlDetector.FindCallbackUrl`, l'écrit
sur le named pipe puis sort par `Environment.Exit(0)`. Aucune
fenêtre n'est créée.
7. L'instance vivante reçoit l'URL via le pipe, complète la TCS,
`OidcClient` échange le code contre les tokens (access +
refresh + id) et les persiste dans `~/.config/PostIt/tokens.json`.
## Pourquoi le scheme custom plutôt que loopback
Le loopback (`http://127.0.0.1:7890/callback`) oblige l'app à
ouvrir un port TCP, à le publier comme redirect URI dans l'IdP,
et à gérer la course "le navigateur revient-il à temps ?".
Le scheme custom transfère la livraison du callback à l'OS :
le navigateur tape sur une URL que l'OS sait router vers PostIt,
pas vers un serveur HTTP.
## Composants partagés (`PostIt/`)
| Composant | Rôle |
|---------------------------------|-------------------------------------------------------------------|
| `Services/OidcLoginPhase` | Enum des étapes du flow : `Idle / Discovering / OpeningBrowser / AwaitingCallback / ExchangingCode / Success / Error` |
| `Services/YavscApiClient` | Client HTTP de l'API Yavsc. Porte `LoginInteractiveAsync(IProgress<OidcLoginPhase>)` et `TrySilentLoginAsync`. Refresh silencieux sur 401 et sur access-token bientôt expiré. |
| `Services/SingleInstance` | Named-pipe helper. `TryHandOffAsync` côté 2ᵉ instance, `StartServerAsync` côté instance vivante. |
| `Services/CustomSchemeBrowser` | `IBrowser` OidcClient qui ouvre le système + attend le pipe. |
| `Services/SchemeUrlDetector` | Détection pure, testable, du `postit://callback` dans argv. |
| `Services/TokenStore` | Persiste `RefreshTokenRecord` (access/refresh/id + expiry). Mode 0600 sur POSIX. |
| `ViewModels/SessionStatusViewModel` | VM du bandeau persistant Connecté/Déconnecté + Logout. |
| `Views/SessionStatusBanner` | Bandeau affiché en haut de `MainWindow`, visible sur toutes les pages. |
## Plateformes (`PostIt.Desktop`, `PostIt.Android`, `PostIt.Browser`)
Chaque plateforme injecte son propre `IBrowser` via
`Platform.CreateBrowser` :
- **Desktop** : `CustomSchemeBrowser` + scheme `postit://`.
- **Android** : Chrome Custom Tabs via `AndroidSystemBrowser`,
scheme `android://postit-signin` (déclaré comme
`<activity-alias>` dans `AndroidManifest.xml`). MainActivity est
`SingleTask``OnNewIntent` livre l'URL à `AndroidOidcCallbackSink`.
- **Browser (WASM)** : pas de process distinct → N/A.
## UX observable
`LoginPage` affiche en continu la **phase** courante du flow
(badge `PhaseLabel`) en plus du `StatusMessage` textuel :
| Phase | Signification |
|--------------------|------------------------------------------------------------------|
| `Discovering` | Fetch de `/.well-known/openid-configuration`. |
| `OpeningBrowser` | Navigateur système ouvert, on attend que l'utilisateur revienne. |
| `AwaitingCallback` | (idem `OpeningBrowser` aujourd'hui — couvre la fenêtre pipe). |
| `ExchangingCode` | Trade du `code` contre les tokens à `/connect/token`. |
| `Success` / `Error`| Issue du flow. |
Si le badge reste bloqué sur `OpeningBrowser`, **l'OS n'a jamais
relancé PostIt avec l'URL `postit://callback`** : le scheme
handler n'est pas enregistré correctement.
## Persistance et reprise au démarrage
Au boot, `App.OnFrameworkInitializationCompleted` :
1. Construit le `TokenStore` (~/.config/PostIt/tokens.json) et
`YavscApiClient`. Ce dernier charge `_tokens = store.Load()`
dans son constructeur.
2. Ouvre `MainWindow` avec `HomePage` comme racine.
3. Sur l'événement `Opened`, appelle `TrySilentLoginAsync` :
- si l'access token a encore `> RefreshSkew` (60 s) de vie →
`Success` immédiat, push `MainPage`.
- sinon, tente un refresh via le refresh token. Si l'IdP
accepte → push `MainPage`. Si l'IdP rejette (révocation,
vol, expiration du refresh) → `_store.Clear()`, retour à
`HomePage`.
Le bandeau `SessionStatusBanner` reflète `Api.HasValidSession`
en continu et expose le bouton **Se déconnecter**, qui appelle
`Api.LogoutAsync()` (purge du store) puis `PopToRootAsync`
pour ramener sur `HomePage`.
## Garanties testées
- `LoginInteractiveAsync` émet `Discovering → OpeningBrowser →
ExchangingCode → Success` (ou `Error` si pas de navigateur).
- `TrySilentLoginAsync` retourne `false` si pas de bundle sur
disque, `true` si l'access est encore valide, `true` après un
refresh réussi.
- `SchemeUrlDetector.FindCallbackUrl` reconnaît l'URL en argv,
case-insensitive, refuse les faux positifs (`postit-…://`),
retourne le premier match de manière déterministe.
Le chemin "refresh token rejeté → purge du store" est testé
manuellement ; le stub `OidcStubAuthority` ne sait pas encore
rejeter un refresh token précis — extension future.
## Voir aussi
- [Architecture.md](../Architecture.md) — racine.

View file

@ -0,0 +1,79 @@
# Workflow multi-parties
> **Récapitulatif** : Yavsc orchestre des projets multi-parties
> (client, fournisseur, coordinateur) avec mise en relation,
> sous-traitance et signature de licence en amont de la production.
> Détail dans cette page, racine de l'architecture : [Architecture.md](../Architecture.md).
## Rôle de chaque partie
| Rôle | Type de compte | Description |
|---------------|--------------------|-----------------------------------------------------------|
| Client | Pro ou particulier | Exprime le besoin, valide les devis, co-signe la licence |
| Fournisseur | Pro | Répond aux besoins, peut sous-traiter |
| Coordinateur | Pro ou particulier | Orchestre le projet sans relation de facturation directe |
## Qui peut initier un projet
Le projet peut être initié par n'importe quelle partie :
- Un **client** (particulier ou pro) qui exprime un besoin
- Un **fournisseur** qui propose une offre ou monte un collectif
- Un **tiers coordinateur** qui orchestre sans être client ni prestataire
## Sous-traitance
Un fournisseur peut faire appel à d'autres fournisseurs,
**avec accord explicite du client**. Chaque sous-traitant :
- Est visible dans le projet
- Co-signe l'accord de licence
- Peut recevoir un devis distinct
## Distinction B2B / B2C
La distinction est portée par le **type de compte** :
- Compte **pro** : SIRET, TVA, facturation professionnelle
- Compte **particulier** : usage personnel, sans obligations fiscales pro
Un projet peut mélanger les deux (ex : un particulier client,
plusieurs prestataires pro) — Yavsc n'impose pas d'homogénéité.
## États d'un projet
```
Initié → En recherche de parties → Devis en cours
→ Accord de licence signé → En production
→ Livré → Publié (si licence libre)
```
## Contrainte clé
L'accord de licence est établi **avant** tout début de production,
signé (ou validé) par toutes les parties : client, fournisseurs,
et sous-traitants éventuels.
## Domaine musical — Titres collaboratifs
Permet la production collaborative de titres musicaux sous licence
libre, à partir de la mise en relation assurée par Yavsc.
### Formats supportés
- Audio (ex : WAV, FLAC, MP3)
- Partition (ex : MusicXML, LilyPond, PDF)
- MIDI
### Flux de production
1. Un client exprime un besoin musical
2. Des prestataires répondent avec des devis
3. Un **consensus est établi en amont** entre le client et les
contributeurs sur la licence du livrable final
4. La collaboration produit les fichiers
5. Le titre est publié sous la licence choisie
## Voir aussi
- [Architecture.md](../Architecture.md) — racine.