Live Versions, Approvals and Loading Rules
A rule in the Rule Repository has any number of versions, and at most one live version: the one your application evaluates. A source object likewise has at most one live version, the one the repository treats as current. Nothing becomes live by accident: a person makes a rule version live in the repository, and your application then loads the live versions when it evaluates. Together, immutable versions, approvals, Go Live and Take Offline make up the repository's decision workflow: the governed path every change takes to production and back, with each step allowed only to the users you give its permission.
You need this page to decide who may put rules into production, how approval works, what the repository checks before a version goes live, what it tells your application when that happens, and how your application loads the live rules for the Rule Engine.
The Model
- A version is immutable. Once saved, a rule version's logic never changes; a change is a new version. Only its name and description can be edited.
- The live version is named by its rule. Making a version live moves the rule's pointer to it, in one step: there is never a moment with two live versions, and two people who try at once cannot both succeed.
- No live version is a real state. A rule nobody has put into production has none, and neither does a rule whose live version was taken offline with nothing to replace it. Your application finds no live version of such a rule.
- A version that has ever been live cannot be deleted, except together with its whole source object. Its history, tests and recorded evaluations stay explainable.
- Only people make rule versions live. AI agents and services never do: every save they make requests approval, whatever their permissions say.
Who May Go Live
What a user may do is decided by the permission set your application gives each request (see Permissions). These permissions govern production:
| Permission |
Lets the user |
RuleVersionGoLive (42) |
Make a rule version live: Go Live in its dialog, or Save & Go Live when saving it |
RuleVersionTakeOffline (43) |
Take a rule's live version offline, with or without a replacement |
RuleVersionRequestApproval (44) |
Ask for approval of a version they cannot make live themselves |
RuleVersionApproveAndDecline (45) |
Approve, approve and go live, decline, or revoke an approval. Requires RuleVersionGoLive |
SourceVersionGoLive (21) |
Make a source version live |
SourceVersionTakeOffline (22) |
Take a source object's live version offline |
A typical setup has three roles:
| Role |
Permissions related to production |
| Author |
RuleVersionRequestApproval |
| Publisher |
RuleVersionGoLive, RuleVersionTakeOffline, RuleVersionApproveAndDecline |
| Administrator |
Everything, including the source version permissions |
using CodeEffects.Rule.Common.Models;
using CodeEffects.Rule.Repository;
// An author: may write rules and ask for approval, may not publish
var author = new Configuration
{
AccessMode = ActionPermissionMode.AllowAllExcept,
ActionList = new HashSet<ActionPermissionType>
{
ActionPermissionType.RuleVersionGoLive,
ActionPermissionType.RuleVersionTakeOffline,
ActionPermissionType.SourceVersionGoLive,
ActionPermissionType.SourceVersionTakeOffline
}
};
Under AllowAllExcept, withholding RuleVersionGoLive also withholds RuleVersionApproveAndDecline, which depends on it. The user interface turns off what a user may not do, and the server refuses it anyway.
Approval
Approval is a step of its own, with four states for each rule version: None, Requested, Approved and Declined.
- Requesting. A user who cannot make a version live is asked, when they save it, whether to request approval. They can also request it later from the version's dialog. Every save by an AI agent or a service requests it.
- Approving. A user with
RuleVersionApproveAndDecline approves the version (it may then go live) or approves it and makes it live in one step (Approve & Go Live). Nobody may approve a request they made themselves for their own direct save.
- Declining. The approver may decline with a reason. The reason is also written as a note on the version, so it stays with it.
- Going live. Making a version live meets its request, however it is done. A live version never requires approval.
What the states allow:
- Only a Requested version is held back: a publisher cannot make it live until it is approved or declined.
- A Declined version can still be made live by a publisher, who overrules the decline.
- An Approved version can have its approval revoked, or be declined.
- A version taken offline can be made live again without a new approval.
Going live, taking offline, approving, approving and going live, declining and revoking approval each ask the user to confirm by typing a word, with an optional note, so production never changes by a stray click.
What Is Checked Before a Version Goes Live
Before a rule version becomes live, the repository checks everything that could break, using its member usage index:
| Check |
Result |
| A reusable rule the version calls is not available: not reusable, not an evaluation rule, or has no live version |
Blocks |
| A call would lead back into the rule itself (a cycle) |
Blocks |
| A rule the version calls uses members the version's source version does not declare |
Blocks |
| A called rule is used where its source object is not the one evaluated (for example, a rule written on a collection's items used outside a condition on that collection) |
Blocks |
| When the rule can be called by others: a rule whose live version calls it would reach members the new version's source does not declare |
Blocks; a warning when it cannot be checked |
| A call cannot be checked |
A warning the user must acknowledge |
Taking a version offline is refused while other rules' live versions call it: they would stop working the moment it went. The user is shown each calling rule and must take those offline first. Taking a version offline with a replacement makes the replacement live and takes the old one offline in one step.
The checks run again inside the write, with the called rules locked, so two people acting at the same moment cannot slip a broken combination past each other.
Source versions have no such checks: a source version's live state does not change what any rule evaluates.
Telling People: the Notify Event
The repository raises an event when something happens that people may need to hear about: approval requested, a request approved or declined, a version made live or taken offline. Your application decides who hears about it and how, because only it knows its users and how to reach them. See Notifications for the full list and a complete example of emailing approvers.
Loading the Live Rules for Evaluation
Your application evaluates rules with the Rule Engine. It gets the live versions from the repository in one of two ways:
| Your application has |
Load rules with |
Evaluations are |
| The Repository library only |
Context.GetRules(dbProvider, query) |
Not recorded |
| The Audit library |
AuditRecorder.GetRules(query) |
Recorded, and shown in the repository's Live Evaluations |
Both take the same RuleQuery and return a ruleset with every rule asked for and every rule those call, each at its live version, in one document the engine reads as it is. The engine needs no rule resolver for the called rules.
using CodeEffects.Rule.Common.Models;
using CodeEffects.Rule.Engine;
using CodeEffects.Rule.Repository;
// Every rule of the Order source object that has a live version
LoadedRuleset ruleset = Context.GetRules(dbProvider, RuleQuery.BySource("Acme.Sales.Order"));
var evaluator = new Evaluator<Order>(ruleset.Xml);
LoadedRule discount = ruleset.Rule("Holiday discount");
bool applies = evaluator.Evaluate(order, discount.Id);
Choosing the Rules
| Query |
Loads |
RuleQuery.ByNames("Holiday discount", "Credit check") |
The rules with these names |
RuleQuery.ByIds(id1, id2) |
The rules with these IDs (the IDs the Rule Editor gives rules) |
RuleQuery.BySource("Acme.Sales.Order") |
Every rule of a source object, by its alias, that has a live version |
RuleQuery.BySource(sourceId) |
The same, by the source object's ID |
.InSource("Acme.Sales.Order") |
Narrows a query by names when names repeat across source objects |
.OfType(true) / .OfType(false) |
Only evaluation rules, or only execution rules |
// Two execution rules of the Order source object
var query = RuleQuery.ByNames("Apply discounts", "Route for review")
.InSource("Acme.Sales.Order")
.OfType(false);
Each LoadedRule says which rule it is (Id, Name, IsEvaluationType), which live version the ruleset holds (RuleVersionId, RuleVersionName), which source version it was written against (SourceVersionId, SourceAlias), and whether it is there only because another rule calls it (IsCalled).
When Loading Fails
GetRules(..) throws a RuleException instead of returning a partial ruleset:
| Control number |
Cause |
| CE10256 |
No rule with a live version matches the query |
| CE10257 |
A rule calls rules that have no live version |
| CE10258 |
A live version holds no rule, or its definition cannot be read |
| CE10259 |
A rule asked for by ID or name does not exist, or has no live version |
| CE10260 |
The query is null |
| CE19004 |
The database is behind the version the library needs; the message names the scripts to run |
Caching and Refreshing
GetRules(..) reads the database every time it is called. Build the evaluator once and keep it while the ruleset is current, rather than loading for every evaluation. Ways to decide when to reload:
- On a timer. Reload every few minutes. Simple, and works in a web farm.
- On the Notify event. Reload when
RuleVersionMadeLive or RuleVersionTakenOffline is raised. The event is raised only in the process that handled the repository's request, so in a web farm, pass it on to the other servers through whatever your application already uses (a message bus, a cache key).
- On deployment. For applications where rules change only with releases.
public sealed class OrderRules
{
private readonly IDbProvider dbProvider;
private readonly object gate = new object();
private Evaluator<Order> evaluator;
private LoadedRuleset ruleset;
public OrderRules(IDbProvider dbProvider) { this.dbProvider = dbProvider; }
/// <summary>Call from the Notify event, a timer, or a deployment step.</summary>
public void Reload()
{
var loaded = Context.GetRules(this.dbProvider, RuleQuery.BySource("Acme.Sales.Order"));
var built = new Evaluator<Order>(loaded.Xml);
lock(this.gate)
{
this.ruleset = loaded;
this.evaluator = built;
}
}
public bool Evaluate(Order order, string ruleName)
{
Evaluator<Order> current;
LoadedRuleset rules;
lock(this.gate)
{
if(this.evaluator == null) this.Reload();
current = this.evaluator;
rules = this.ruleset;
}
var rule = rules.Rule(ruleName) ?? throw new InvalidOperationException("No live rule named " + ruleName);
return current.Evaluate(order, rule.Id);
}
}
Source Versions Going Live
A source object's live version is the one new rules and tests are built on. The first version of every source object is live as soon as it is synced. After that, SourceSettings.NewVersionsGoLive decides: left false, a new version waits for a person with SourceVersionGoLive to make it live in the repository; set true, the sync makes it live at once. See Source Synchronization and Reindexing.