Skip to content

Rule Versioning

Matching rules are versioned independently of the spec. Every time you create a rule via the API, the system auto-increments the version number for that rule name. Only one version per name can be active at a time — activating a new version automatically deactivates the previous one.

How It Works

Each MatchRule has these version-relevant fields:

FieldTypeDescription
namestringRule identifier (e.g., "email_exact")
versionintegerAuto-incremented per rule name
is_activebooleanOnly one version per name can be true
rule_typestringRule type ("exact", "similarity", "composite")
configJSONRule-specific configuration
weightfloatScoring weight (0.0–1.0)
created_byUUIDUser who created this version
created_attimestampWhen this version was created

When you create a rule with a name that already exists, the system:

  1. Assigns the next version number (e.g., v1 exists → new rule gets v2)
  2. If is_active: true, deactivates all other versions of that name
  3. Stores the new version — previous versions are preserved, never deleted

Creating a Rule Version

v1: Initial Rule

python
import httpx

resp = httpx.post("https://api.kanoniv.com/v1/rules",
    headers={"X-API-Key": "kn_...", "Content-Type": "application/json"},
    json={
        "name": "email_exact",
        "rule_type": "exact",
        "config": {"field": "email", "case_sensitive": False},
        "weight": 1.0,
        "is_active": True
    })
rule = resp.json()
print(f"Created {rule['name']} v{rule['version']}")
# Created email_exact v1
bash
curl -X POST "https://api.kanoniv.com/v1/rules" \
  -H "X-API-Key: kn_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "email_exact",
    "rule_type": "exact",
    "config": {"field": "email", "case_sensitive": false},
    "weight": 1.0,
    "is_active": true
  }'

v2: Update the Rule

Later you decide case-sensitive matching is needed and increase the weight:

python
resp = httpx.post("https://api.kanoniv.com/v1/rules",
    headers={"X-API-Key": "kn_...", "Content-Type": "application/json"},
    json={
        "name": "email_exact",
        "rule_type": "exact",
        "config": {"field": "email", "case_sensitive": True},
        "weight": 0.95,
        "is_active": True   # automatically deactivates v1
    })
rule = resp.json()
print(f"Created {rule['name']} v{rule['version']}")
# Created email_exact v2
bash
curl -X POST "https://api.kanoniv.com/v1/rules" \
  -H "X-API-Key: kn_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "email_exact",
    "rule_type": "exact",
    "config": {"field": "email", "case_sensitive": true},
    "weight": 0.95,
    "is_active": true
  }'

After this, v1 is is_active: false and v2 is is_active: true. Both are stored.

Viewing Rule History

The history endpoint returns all versions of a rule, ordered by version number:

python
resp = httpx.get("https://api.kanoniv.com/v1/rules/email_exact/history",
    headers={"X-API-Key": "kn_..."})
history = resp.json()

for v in history:
    status = "ACTIVE" if v["is_active"] else "inactive"
    print(f"  v{v['version']}: weight={v['weight']}, "
          f"case_sensitive={v['config']['case_sensitive']}, {status}")
bash
curl "https://api.kanoniv.com/v1/rules/email_exact/history" \
  -H "X-API-Key: kn_..."

Example output:

  v2: weight=0.95, case_sensitive=True, ACTIVE
  v1: weight=1.0, case_sensitive=False, inactive

Testing Before Activating

Create a new version with is_active: false to test it without affecting production:

python
# Create v3 as inactive — v2 stays active
resp = httpx.post("https://api.kanoniv.com/v1/rules",
    headers={"X-API-Key": "kn_...", "Content-Type": "application/json"},
    json={
        "name": "email_exact",
        "rule_type": "exact",
        "config": {"field": "email", "case_sensitive": True, "normalize": True},
        "weight": 0.9,
        "is_active": False   # does NOT deactivate v2
    })
print(f"Created v{resp.json()['version']} (inactive, v2 still active)")

Once you've validated the new version works as expected, activate it by creating the next version with is_active: true, or by creating a new version that copies v3's config with is_active: true.

Rolling Back

There's no dedicated rollback endpoint. Instead, create a new version with the old config:

python
# v2 is active but causing issues. Roll back to v1's config.
resp = httpx.post("https://api.kanoniv.com/v1/rules",
    headers={"X-API-Key": "kn_...", "Content-Type": "application/json"},
    json={
        "name": "email_exact",
        "rule_type": "exact",
        "config": {"field": "email", "case_sensitive": False},  # same as v1
        "weight": 1.0,                                          # same as v1
        "is_active": True
    })
print(f"Rolled back — created v{resp.json()['version']} with v1 config")

This creates v3 (or v4, depending on history) with v1's configuration. The full history is preserved:

v4: weight=1.0, case_sensitive=False, ACTIVE    ← rollback
v3: weight=0.9, case_sensitive=True, inactive    ← test version
v2: weight=0.95, case_sensitive=True, inactive   ← the bad version
v1: weight=1.0, case_sensitive=False, inactive   ← original

Rule Versioning vs. Spec Versioning

Rule VersioningSpec Versioning
ScopeSingle matching ruleEntire resolution logic (all rules, sources, thresholds, survivorship)
Version formatAuto-incrementing integerUser-defined string (customer-v3) + computed SHA-256 hash
Change detectionCompare version numbers or config JSONCompare plan_hash values or use diff()
Managed viaPOST /v1/rules APIPOST /v1/identity/specs API or Spec.from_file() + SDK
HistoryGET /v1/rules/:name/historyGET /v1/identity/specs (list all versions)
RollbackCreate new version with old configUpload old spec YAML again

Rule versioning is useful when you're tuning individual rules via the API (e.g., adjusting weights in production). Spec versioning captures the full picture — if you manage rules through YAML specs, the spec's plan_hash already tracks rule changes.

See Also

The identity and delegation layer for AI agents.