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

OperatorMeans
eqequals
neis different from
gtis greater than
gteis greater than or equal to
ltis less than
lteis less than or equal to
inis one of
not_inis not one of
emptyis empty
not_emptyis not empty
containscontains

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.

bloquerBlock (refuses the write)
avertirWarn (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 with condition and severity
{
  "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|…).

IndicatorEntityMeans
supplier.otdSupplieron-time receipts / total receipts
supplier.retard_moyen_jSupplieraverage delay of late receipts, in days
supplier.taux_ncSuppliernonconformities / receipts
supplier.noteSupplierlatest evaluation score and its age
article.delai_reel_vs_annonceItemgap between actual and announced lead time
workstation.couverture_qualifWorkstationshare of assigned operators qualified at the required level
employee.qualif_posteEmployee × workstationassigned, required level, certification current?
workstation.pannes_90jWorkstationnumber of breakdowns in the window
workstation.mtbf_hWorkstationmean time between failures, in hours
workstation.disponibiliteWorkstationaverage workstation availability over the window
carrier.retardCarriershare of late deliveries
article.taux_ncItemnonconformities / 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