Deploying Business Rules
Observe rights, fix, arm module by module; set rules with condition and severity; declare sensitive fields; read risk signals; lean on the copilot.
This page walks through deploying business rules and rights on a new tenant, in the order a consultant follows it: observe before blocking, configure condition and severity rather than multiplying rule types, declare what must be logged, read what the risk engine already knows, and let the copilot explain a refusal in the user's own language.
Rights: observe, fix, arm
Two settings control how rights are enforced: AUTH_GUARD_MODE for write actions and AUTH_GUARD_READ_MODE for reads, each with three values: off (no check), observe (the refusal is logged but let through) and enforce (the refusal actually blocks). Both default to off.
Step 1 — observe: set AUTH_GUARD_MODE=observe on a representative period and read GET /api/v1/admin/auth-guard/observations, which returns the current mode, the read mode, the modules already armed, and a count of the refusals that would have happened per module and per action.
Step 2 — fix: for each refusal that shows up, decide whether it's a legitimate gap (a role missing a right it should have) and correct the role, or a refusal the tenant actually wants once armed.
Step 3 — arm, module by module, through AUTH_GUARD_ENFORCE_MODULES, a list shared between writes and reads — never all at once. The volume of observed reads is sampled (AUTH_GUARD_READ_LOG_SAMPLE, 100 by default) to keep the logs from flooding.
Once armed, a right refusal answers 403 with the code droit_insuffisant — actionable, unlike a plain generic error. A module outside the subscription answers 404: the user doesn’t even know it exists.
Rights say who can act on this module; once they are armed, a status profile can go one step further and say who can push this specific document from this status to that one — restricting an already-coded transition graph, never widening it.
Condition and severity
Restricting a rule: the application condition
Any rule of any type can carry an application condition, generic at the registry level — no rule type needs to recode it. A condition is { champ, op, valeur }; several conditions combine with AND, either as a single condition object or a conditions array in params. Without a condition, the rule always applies within its scope.
The condition is evaluated on the state after the write, completed by a re-read of the current row when a condition field is missing from the submitted body — a field the client didn’t send is not necessarily unset. When the condition isn’t met, the rule isn’t called at all: the write passes.
The operators
| Operator | Means |
|---|---|
eq | equals |
ne | is different from |
gt | is greater than |
gte | is greater than or equal to |
lt | is less than |
lte | is less than or equal to |
in | is one of |
not_in | is not one of |
empty | is empty |
not_empty | is not empty |
contains | contains |
Warn, then block: severity
Every rule type also reads a generic mode parameter: bloquer (the default) or avertir. In avertir mode, a refusal is applied after the rule runs — it is logged and counted, then erased: the write goes through anyway.
bloquer | Block (refuses the write) |
avertir | Warn (logs and lets it through) |
Warnings are counted per rule (key regle/<code>), read alongside rights observations on GET /api/v1/admin/auth-guard/observations — the same instrument, the same discipline: observe the count before hardening a rule from avertir to bloquer.
{
"rule_type": "approbation_montant",
"scope_type": "tenant",
"params": {
"table": "purchase_orders",
"paliers": ["10000:2", "100000:3"],
"mode": "avertir",
"conditions": [
{ "champ": "warehouse_id", "op": "eq", "valeur": "WH-EST" }
]
}
}Sensitive fields
Every change to a field declared sensitive is logged: field, old value, new value, author and date. The mechanism relies on a declaration table (sensitive_fields) and a log table (field_changes), fed by a generic trigger — a newly declared field is logged without any extra application code.
A consultant declares an additional field to log through the sensitive-fields resource, on the Logged fields screen next to Business Rules under Settings → System: pick the table (suppliers, customers, items, employees, profiles, user rights) and the field. Bank values (account number, IBAN) stay masked except for the last 4 characters. A declared field can be turned off without losing the history already written; it is read on the History tab of the supplier, customer or item record.
Risk signals
A dedicated engine (internal/risk) computes twelve indicators — supplier reliability, item lead time, workstation availability and breakdowns, carrier delays — each over a default 90-day window, configurable. Thresholds are not constants: the couche-2 type seuil_risque parametrizes them per indicator (attention, critique, fenetre_jours, echantillon_min), at tenant, supplier, item or workstation scope. Without a configured rule, built-in defaults apply, and the response says which source answered (seuil_source: defaut|tenant|…).
| Indicator | Entity | Means |
|---|---|---|
supplier.otd | Supplier | on-time receipts / total receipts |
supplier.retard_moyen_j | Supplier | average delay of late receipts, in days |
supplier.taux_nc | Supplier | nonconformities / receipts |
supplier.note | Supplier | latest evaluation score and its age |
article.delai_reel_vs_annonce | Item | gap between actual and announced lead time |
workstation.couverture_qualif | Workstation | share of assigned operators qualified at the required level |
employee.qualif_poste | Employee × workstation | assigned, required level, certification current? |
workstation.pannes_90j | Workstation | number of breakdowns in the window |
workstation.mtbf_h | Workstation | mean time between failures, in hours |
workstation.disponibilite | Workstation | average workstation availability over the window |
carrier.retard | Carrier | share of late deliveries |
article.taux_nc | Item | nonconformities / completed manufacturing orders |
Each signal returns { code, valeur, unite, fenetre_j, n_echantillon, preuve[] } — the proof is 3 to 5 supporting documents (number, date, gap), never a bare score. A sample below echantillon_min answers insuffisant, never a false green light.
Endpoints: GET /api/v1/risks/{entite}/{id}?fenetre=90 for a single entity; POST /api/v1/risks/evaluate for a flow-designer node list before saving; GET /api/v1/risks/flow/{flowId}, /manufacturing-order/{id} and /affair/{id} for the same evaluation over a saved flow, an OF, or an affair — returning the five most likely problems, sorted critical first. Cached 15 minutes per (tenant, entity, id, window).
The copilot
The copilot reads the same structured refusal ({ code, params, message }) that a rule produces, to explain it in plain language and guide the user toward the fix — changing the received quantity, choosing another period, requesting an approval. The prompt restricts it to only the context provided: the copilot explains, it does not compute its own numbers.
On the flow-designer and on an affair page, the same alert list the risk engine computes is injected into the copilot’s context — the same JSON the panel shows, nothing recalculated on the AI side. Ask it “why is this node red” and it reads the indicator, the threshold crossed, and the supporting documents already returned by the engine.
The dedicated copilot page goes further: “Why the last refusal?” on any screen, “Guide me” for the three end-to-end procedures with a stepper and “Do it for me”, and what the copilot never does.
← Back to Business Rules and Control
See also : Type catalog · Packs by trade · Copilot · Status profiles