Metadata-Version: 2.5
Name: facturseal-ci
Version: 0.0.1
Summary: Client de CI de FacturSeal : non-régression de conformité Factur-X / EN 16931
Project-URL: Homepage, https://facturseal.com
Project-URL: Documentation, https://facturseal.com/docs/ci
Project-URL: Source, https://gitlab.com/agapornis-ct/facturseal
Project-URL: Changelog, https://gitlab.com/agapornis-ct/facturseal/-/blob/main/clients/facturseal-ci/CHANGELOG.md
Author: Tsharp / FacturSeal
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: ci,en16931,factur-x,facturation-electronique,non-regression
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: hatchling>=1.24; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# facturseal-ci

Client de CI de [FacturSeal](https://facturseal.com) : il fait échouer votre pipeline
quand une facture qui était conforme à l'EN 16931 / Factur-X cesse de l'être.

```bash
pip install facturseal-ci
```

Aucune dépendance, aucun moteur, aucune image Docker : le paquet ne contient que le
strict nécessaire pour générer un corpus avec **votre** code, l'envoyer au service et
lire le verdict. Il s'installe en une seconde dans un runner.

## En une commande

```bash
export FACTURSEAL_URL=https://api.facturseal.com
export FACTURSEAL_TOKEN=$FS_TOKEN   # secret de projet

facturseal-ci \
  --generate "php examples/php/generate.php" \
  --corpus build/factures \
  --baseline facturseal-baseline.json \
  --report build/facturseal.md \
  --junit build/facturseal-junit.xml
```

Au premier run, la baseline de référence est écrite : **commitez-la**. Aux runs
suivants, elle est comparée au corpus régénéré.

## Codes de sortie

| Code | Signification |
|------|---------------|
| `0`  | Aucune régression retenue |
| `1`  | Régression retenue : une facture qui passait casse, le pipeline échoue |
| `2`  | Panne d'outil : service injoignable, corpus non validable, réponse illisible |

`2` n'est jamais `1`, et c'est le point : un service indisponible n'est pas une
facture non conforme. Une CI qui confondrait les deux enverrait votre équipe chercher
un bug dans son code de facturation.

## Le contrat de génération

`--generate` reçoit une commande, en **n'importe quel langage**. FacturSeal n'exécute
jamais votre code métier : il vous passe un scénario et attend une facture.

Pour chaque scénario servi par le service, votre commande est appelée avec :

- `$FS_SCENARIO_PAYLOAD` : chemin d'un fichier JSON décrivant la facture à produire,
  en termes métier BT/BG (jamais du XML) ;
- `$FS_INVOICE_OUT` : chemin où écrire la facture produite.

Elle doit sortir en `0` **si et seulement si** le fichier a été écrit.

```php
<?php
// examples/php/generate.php
$payload = json_decode(file_get_contents(getenv('FS_SCENARIO_PAYLOAD')), true);
$xml = MonEditeur\Facturation::genererFacturX($payload['blueprint']);
file_put_contents(getenv('FS_INVOICE_OUT'), $xml);
```

Le catalogue de scénarios est **servi par l'API** (`GET /me/scenarios`), pas embarqué
dans ce paquet : il évolue avec la réglementation sans que vous ayez à réinstaller
quoi que ce soit.

Si votre corpus est déjà produit par ailleurs, omettez `--generate` : le contenu de
`--corpus` est envoyé tel quel.

## Options

| Option | Rôle |
|--------|------|
| `--generate CMD` | Commande de génération de l'éditeur ; absente = corpus déjà peuplé |
| `--corpus DIR` | Répertoire du corpus (défaut : `build/factures`) |
| `--baseline FILE` | Baseline de référence versionnée (défaut : `facturseal-baseline.json`) |
| `--extension EXT` | Extension produite : `xml` ou `pdf` (défaut : `xml`) |
| `--api-url URL` | URL du service (déf. `FACTURSEAL_URL`) |
| `--token TOKEN` | Jeton bearer (déf. `FACTURSEAL_TOKEN`) |
| `--report FILE` | Rapport markdown, lisible en revue de merge request |
| `--junit FILE` | JUnit XML : onglet « Tests » de GitLab et GitHub |
| `--sarif FILE` | SARIF 2.1.0 : onglet « Security / code scanning » de GitHub |
| `--update-baseline` | Promouvoir : (re)figer la référence au lieu de differ |
| `--waivers FILE` | Fichier de dérogations gouvernées (déf. `FS_WAIVER_STORE`) |
| `--tenant ID` | Tenant dont les dérogations s'appliquent (exigé avec `--waivers`) |
| `--allow-insecure` | Tolérer un service en `http` (le jeton circule alors en clair) |
| `--timeout N` | Budget en secondes par appel et par génération (défaut : 120) |

Les métadonnées de run (branche, commit, déclencheur) sont auto-détectées sur GitLab
CI, GitHub Actions et Azure DevOps ; `--branch`, `--commit` et `--trigger` les
forcent au besoin.

## Dérogations

Une régression peut être **acceptée** sans être effacée. Le fichier de dérogations est
versionné dans votre dépôt, par dessein : une dérogation est une décision, elle passe
donc par la revue de code comme le reste.

```json
{
  "waivers": {
    "mon-tenant": [
      {
        "rule_id": "BR-FR-01_BT-1",
        "justification": "Correctif livré en 2026-09, ticket FACT-4821",
        "created_at": "2026-08-11",
        "expires_at": "2026-09-30",
        "author": "equipe-facturation"
      }
    ]
  }
}
```

```bash
facturseal-ci --waivers .facturseal/waivers.json --tenant mon-tenant ...
```

Trois propriétés valent d'être connues :

- **la maille est l'assertion, pas la règle.** Sous la couche FR, une règle se décline
  en un assert par champ contrôlé : `BR-FR-01` ne déroge à **rien**, `BR-FR-01_BT-1`
  déroge à ce champ-là. Élargir tacitement à toute une famille dérogerait à des
  contrôles que personne n'a nommés ;
- **un constat dérogé reste nommé.** Il sort de la liste bloquante, il reste dans le
  rapport, dans le JUnit (`skipped`, avec sa justification) et dans le SARIF
  (`suppressions`). « Rien trouvé » et « trouvé puis accepté » ne se confondent pas ;
- **les dérogations mortes sont dénoncées.** Une dérogation active qui n'a couvert
  aucun constat, ou une dérogation échue, est listée en sortie : sans ça, elle
  élargirait le plafond sans que personne l'ait décidé.

Un fichier de dérogations demandé mais absent est une **panne d'outil** (exit 2), pas
« aucune dérogation ».

## GitLab CI

```yaml
facturseal:
  image: python:3.12-slim
  script:
    - pip install facturseal-ci
    - facturseal-ci --generate "php examples/php/generate.php"
                    --junit build/facturseal-junit.xml
                    --report build/facturseal.md
  artifacts:
    when: always
    paths: [build/facturseal.md]
    reports:
      junit: build/facturseal-junit.xml
```

## GitHub Actions

```yaml
- run: pip install facturseal-ci
- run: facturseal-ci --generate "php examples/php/generate.php" --sarif facturseal.sarif
  env:
    FACTURSEAL_URL: ${{ vars.FACTURSEAL_URL }}
    FACTURSEAL_TOKEN: ${{ secrets.FACTURSEAL_TOKEN }}
- uses: github/codeql-action/upload-sarif@v3
  if: always()
  with: { sarif_file: facturseal.sarif }
```

## Ce que ce paquet ne contient pas

Ni moteur de validation, ni catalogue de règles, ni matrice de référence, ni
scénarios : tout cela appartient au produit FacturSeal et est servi par le service.
La baseline elle-même est **opaque** au client, qui la transporte entre le service et
votre dépôt sans l'interpréter, à la seule exception du verdict par facture, lu pour
détecter les factures que le moteur n'a pas su valider.

## Licence

Apache 2.0 (voir `LICENSE` et `NOTICE`). « FacturSeal » est une marque de Tsharp : la
licence n'en concède pas l'usage.
