Skip to main content

facturseal-ci

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

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

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
// Le connecteur, en entier. La seule pièce à écrire chez vous est la ligne du milieu.
$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);

Un générateur de référence complet, en PHP nu (ni Composer ni framework), est publié dans le dépôt d'exemples sous examples/php/generate.php, avec sept payloads figés (examples/payloads/) qui permettent de développer et tester votre connecteur hors ligne, sans jeton ni réseau : catégorie S, exonéré, autoliquidation, avoir, acompte, multi-taux, remises au niveau document.

Le payload est un asdict complet : toutes les clés optionnelles sont présentes, à null, "" ou [], et les montants sont des chaînes ("240.00", jamais 240.0). Tester la présence d'une clé ne dit donc rien ; il faut tester sa valeur.

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. --cius core|fr-ctc choisit la couche de juridiction servie ; sans elle, l'instance sert sa couche de déploiement. Un corpus généré sous le socle CEN n'exerce aucune des règles BR-FR-* sous lesquelles il serait ensuite validé.

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

Un scénario que votre chaîne ne sait pas produire

Sortez en non-zéro sans écrire de fichier, avec un message qui nomme le champ manquant. C'est le canal prévu, et il ne fait pas échouer le pipeline :

  • le scénario est compté « non produit par l'outil », avec votre message ;
  • il monte au service avec le corpus, et l'écran de couverture le distingue de « non couvert ». Sans cela, les règles que ce cas aurait exercées vous seraient reprochées comme un trou de votre corpus, alors que le cas existe au catalogue et que seul votre générateur peut le combler ;
  • il apparaît dans le rapport markdown, en <skipped> JUnit et en FS-NOT-PRODUCED SARIF (niveau warning) ;
  • le run ne devient pas rouge pour autant : ce n'est pas une non-conformité.
if (!$modele->supporte('BT-31')) {
    fwrite(STDERR, "champ BT-31 (SIRET vendeur) absent du modele de facturation");
    exit(4);
}

Deux choses restent des pannes, et arrêtent la génération :

  • écrire le fichier ET sortir en non-zéro : une facture partielle validée comme entière vous imputerait une non-conformité que votre propre générateur a signalée. Le fichier est retiré ;
  • décliner tous les scénarios : un corpus vide rendrait un diff sans delta, donc un pipeline vert qui ne mesure rien.

Vérifier votre connecteur

facturseal-adapter-check -- php generate.php

Rejoue des payloads de référence hors ligne (ni jeton ni réseau) et vérifie les invariants du contrat : fichier écrit au bon chemin, code de sortie cohérent avec la production du fichier, tenue du budget de temps, absence de fuite d'environnement. C'est la commande à passer avant de brancher --generate dans un pipeline, et celle qui rend le connecteur reprenable par un autre éditeur que celui qui l'a écrit.

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)
--cius LAYER Couche de juridiction du catalogue demandé : core ou fr-ctc (déf. FACTURSEAL_CIUS)
--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.

{
  "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"
      }
    ]
  }
}
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

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

- 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

facturseal_ci-0.0.2.tar.gz (59.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

facturseal_ci-0.0.2-py3-none-any.whl (48.5 kB view details)

Uploaded Python 3

File details

Details for the file facturseal_ci-0.0.2.tar.gz.

File metadata

  • Download URL: facturseal_ci-0.0.2.tar.gz
  • Upload date:
  • Size: 59.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.12.14

File hashes

Hashes for facturseal_ci-0.0.2.tar.gz
Algorithm Hash digest
SHA256 dad0e03f1c3b1cb20815c32b284afb3018dbf6398ac22077e542a81e04c48277
MD5 0205fd692169c54914e7173d0557686b
BLAKE2b-256 9d8b1169de6ca232a806dc0608e57a49d81f807f2230387949620e3e0ae1ad8e

See more details on using hashes here.

File details

Details for the file facturseal_ci-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: facturseal_ci-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 48.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.12.14

File hashes

Hashes for facturseal_ci-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0fe0ed1a5861e0ad2a8e1cd3b1f25500e4e4ea58628e10fcc3b6f955c7512dab
MD5 4c88268d7ab745c82cecc553a7553cc9
BLAKE2b-256 22423328c582c436efa3e048b79a43c84d61218e23c69742d51b6782157b9b42

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 files

0.0.1

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page