Product · avancé — un service juridique
Le cas. Ouvrir une société, puis, en option, souscrire sa comptabilité. Ou souscrire la comptabilité seule, pour une société qui existe déjà.
Ce que l'exemple montre. Un item qui est une demande remplie, pas un produit : la garde (formulaire → éligibilité → devis) est le service. Deux parcours partagent une skill. La comptabilité lit le résultat de l'ouverture de la société dans le même submit.
examples/actuelle/product-advanced.yaml
# Product · advanced — a legal service: open a company, then (optionally) its accounting.
# The item is a FILLED REQUEST, not a product: the select guard (form → eligibility → quote) is the
# product. Two journeys share one skill; the accounting reads the company the formation opened.
station:
id: statuts-express
name: Statuts Express
global:
language: fr
currency: EUR
locale: fr-FR
domain:
domain_type: legaltech
entity_name: service
entity_name_plural: services
agent:
tone: professional
name: Statuts Express
persona: "un assistant juridique précis qui accompagne les fondateurs"
rules:
- "Ne jamais donner de conseil juridique contraignant : présenter les options, laisser les outils valider et tarifer."
- "Citer le nom et le prix exacts renvoyés par les outils, en euros."
portals:
web:
widget:
origins: [https://statuts-express.example.fr]
apis:
- name: juridique
openapi_url: https://api.statuts-express.example.fr/openapi.json
base_url: https://api.statuts-express.example.fr
operations:
- {id: recommendLegalStructure, access: read}
- {id: checkFormationEligibility, access: read}
- {id: quoteFormation, access: read}
- {id: openFormationDossier, access: write, reversible: false}
- {id: getAccountingPlans, access: read}
- {id: checkAccountingEligibility, access: read}
- {id: quoteAccounting, access: read}
- {id: subscribeAccounting, access: write, reversible: cancelSubscription}
- {id: cancelSubscription, access: write}
auth: {type: api_key, key_name: Authorization, key_location: header, key_env: STATUTS_KEY}
skills:
- name: formation
auto_trigger: open-company
cognitive:
objective: "recommander la structure juridique adaptée au projet"
diagnose: [l'activité, le nombre d'associés, le capital]
invoke: {method: recommendLegalStructure}
kinds: {company_formation: {}}
present:
fields:
name: {from: label, type: text, role: title}
price: {from: base_fee, type: price, role: billed}
select:
max_items: 1 # one company is being formed
guards:
- form:
activity: {type: text, required: true, label: "Activité de la société"}
capital: {type: number, required: true, label: "Capital social (€)"}
associates: {type: integer, required: true, label: "Nombre d'associés"}
hq_address: {type: text, required: true, label: "Adresse du siège"}
- check: checkFormationEligibility # the tenant says whether this structure fits
- update: {price: quoteFormation} # …and what it costs for THIS request
submit:
- name: open
mode: request
tool: openFormationDossier
args:
body.structure: "$item.fields.code"
body.activity: "$item.fields.activity"
body.capital: "$item.fields.capital"
body.associates: "$item.fields.associates"
body.hq_address: "$item.fields.hq_address"
body.fee: "$item.price"
body.idempotency_key: "$action.key"
output: {company_id: "$output.company_id"}
render: {component: cards}
- name: accounting
cognitive:
objective: "recommander le plan de comptabilité adapté au chiffre d'affaires"
diagnose: [le chiffre d'affaires estimé, la structure choisie]
invoke: {method: getAccountingPlans}
kinds: {accounting_subscription: {}}
present:
fields:
name: {from: label, type: text, role: title}
price: {from: price, type: price, role: billed}
select:
max_items: 1
guards:
- form:
est_revenue: {type: number, required: true, label: "Chiffre d'affaires estimé (€)"}
siren: {type: text, required: false, label: "SIREN (société existante)"}
- check: checkAccountingEligibility
- update: {price: quoteAccounting}
submit:
- name: subscribe
mode: request
tool: subscribeAccounting
args:
body.plan: "$item.fields.code"
body.est_revenue: "$item.fields.est_revenue"
body.siren: "$item.fields.siren"
# Value at submit (ADR-028 §3): filed AFTER the formation, in the same submit, and held
# if the formation failed. Alone, it resolves empty and the SIREN is used.
body.company_id: "$submitted[formation.open].company_id"
body.fee: "$item.price"
body.fee_cadence: "$item.price_cadence"
render: {component: cards}
playbooks:
- name: open-company
goal: "ouvrir une société : la structure, puis (option) la comptabilité"
tasks:
formation: {skill: formation, required: true, label: "Votre société"}
accounting: {skill: accounting, required: false, label: "Votre comptabilité", after: [formation]}
- name: subscribe-accounting
goal: "souscrire la comptabilité d'une société existante (SIREN)"
tasks:
accounting: {skill: accounting, required: true, label: "Votre comptabilité"}
submit_guard:
form:
accept_terms: {type: boolean, required: true, label: "J'accepte les conditions générales"}
confirm: true
Ce qui se passe
| étape | Core | résultat |
|---|---|---|
| « je crée une SAS avec un associé » | formation → recommendLegalStructure; auto_trigger ouvre open-company | cartes des structures |
| un plan comptable demandé d'abord | step_open : la tâche accounting a after: [formation] | refusé, avec la raison : la société d'abord |
| SAS choisie | formulaire (activité, capital, associés, siège), puis checkFormationEligibility, puis quoteFormation | la ligne entre avec son prix |
| un plan comptable choisi | formulaire, éligibilité, devis | deuxième ligne, prix mensuel |
| « Finaliser » | accept_terms + confirm | openFormationDossier, puis subscribeAccounting avec company_id |
| l'ouverture échoue | ADR-028 §3 | la comptabilité est mise en attente, jamais déposée sur une société absente |
| « j'ai déjà une SARL, SIREN 812… » | l'agent appelle start_playbook : subscribe-accounting | company_id vide, le SIREN est utilisé |
À retenir
$submitted[formation.open].company_idest une dépendance à l'exécution : même submit, dans l'ordre, et rien n'est déposé si le parent a échoué.- Le prix vient du tenant, pour cette demande :
update: {price: quoteFormation}, transmis parbody.fee. openFormationDossierest déclaréereversible: false: la porte MCP annonce le submit comme destructif.subscribeAccountingnomme l'opération qui l'annule (cancelSubscription) ; rien ne l'appelle encore.- Dans
open-company,after: [formation]impose l'ordre au choix : la comptabilité ne s'ouvre qu'une fois la société choisie. La version cible fait de l'ouverture un sous-playbook.