Spec Versioning
Every identity spec has two version identifiers: identity_version (a human-readable string you choose) and plan_hash (a SHA-256 of the compiled plan, computed automatically). Together they let you track what changed and whether it affects reconciliation behavior.
identity_version
A user-defined string set in the spec YAML. Convention is {entity}-v{n}:
identity_version: customer-v3
entity: customer
sources:
# ...The identity_version is how you reference a specific spec via the API:
curl "https://api.kanoniv.com/v1/identity/specs/customer-v3" \
-H "X-API-Key: kn_..."There are no format restrictions — v1, customer-v3, 2026-02-prod, and experiment-A are all valid. Choose a convention that works for your team and stick with it.
plan_hash
The plan_hash is a SHA-256 digest of the compiled IdentityPlan. It's computed automatically when you compile a spec:
from kanoniv import Spec, plan
spec = Spec.from_file("customer-v3.yaml")
result = plan(spec)
print(result.plan_hash) # e.g. "a1b2c3d4e5f6..."The hash captures everything that affects reconciliation behavior: rules, weights, thresholds, blocking configuration, survivorship strategy, and source definitions. Cosmetic changes (description, metadata tags) do not change the hash.
This means:
- Same hash = identical reconciliation behavior. You can safely skip re-processing.
- Different hash = something changed that affects how entities are matched, merged, or scored.
What's Included in the Hash
The hash is computed from the serialized IdentityPlan, which includes:
| Included | Not Included |
|---|---|
| Entity type | Spec description |
| Source definitions and field mappings | Metadata name/owner |
| Blocking strategy and keys | Metadata tags |
| Match rules, weights, algorithms | Comments in YAML |
| Decision thresholds | |
| Survivorship strategy | |
| Exclusions, compliance, hierarchy |
Diffing Specs
The diff() function compares two specs and returns a DiffResult with granular change detection across every section.
from kanoniv import Spec, diff
old = Spec.from_file("customer-v2.yaml")
new = Spec.from_file("customer-v3.yaml")
changes = diff(old, new)
if not changes.has_changes:
print("Specs are identical")
else:
print(changes.summary)DiffResult Properties
| Property | Type | Description |
|---|---|---|
rules_added | list[str] | Rule names present in new spec but not old |
rules_removed | list[str] | Rule names present in old spec but not new |
rules_modified | list[dict] | Rules in both specs with changed fields. Each dict: name, field, old_value, new_value |
sources_added | list[str] | Source names added |
sources_removed | list[str] | Source names removed |
sources_modified | list[dict] | Sources with changed fields. Each dict: name, field, old_value, new_value |
entity_changed | bool | Whether the entity definition changed |
entity_changes | list[dict] | Field-level entity changes. Each dict: path, old_value, new_value |
blocking_changed | bool | Whether blocking strategy or keys changed |
blocking_changes | list[dict] | Field-level blocking changes |
thresholds_changed | bool | Whether decision thresholds changed |
decision_changes | list[dict] | Field-level decision changes |
survivorship_changed | bool | Whether survivorship rules changed |
survivorship_changes | list[dict] | Field-level survivorship changes |
scoring_changed | bool | Whether scoring model configuration changed |
scoring_changes | list[dict] | Field-level scoring changes |
metadata_changed | bool | Whether spec metadata changed |
metadata_changes | list[dict] | Field-level metadata changes |
version_changed | bool | Whether identity_version changed |
has_changes | bool | True if any part of the spec changed |
summary | str | Human-readable summary of all changes |
Example: Inspecting a Diff
from kanoniv import Spec, diff
changes = diff(
Spec.from_file("customer-v2.yaml"),
Spec.from_file("customer-v3.yaml"),
)
if changes.rules_added:
print(f"New rules: {', '.join(changes.rules_added)}")
if changes.thresholds_changed:
for c in changes.decision_changes:
print(f" {c['path']}: {c['old_value']} -> {c['new_value']}")
if changes.survivorship_changed:
for c in changes.survivorship_changes:
print(f" survivorship {c['path']}: {c['old_value']} -> {c['new_value']}")API Endpoints
Upload and Compile a Spec
POST /v1/identity/specsimport httpx
spec_yaml = open("customer-v3.yaml").read()
resp = httpx.post("https://api.kanoniv.com/v1/identity/specs",
headers={"X-API-Key": "kn_...", "Content-Type": "application/json"},
json={"raw_yaml": spec_yaml, "compile": True})
result = resp.json()
if result["valid"]:
print(f"Compiled. plan_hash: {result['plan_hash']}")
else:
print(f"Errors: {result['errors']}")curl -X POST "https://api.kanoniv.com/v1/identity/specs" \
-H "X-API-Key: kn_..." \
-H "Content-Type: application/json" \
-d "{\"raw_yaml\": \"$(cat customer-v3.yaml)\", \"compile\": true}"Response:
{
"valid": true,
"warnings": [],
"errors": [],
"plan_hash": "a1b2c3d4e5f6..."
}List All Spec Versions
GET /v1/identity/specs
GET /v1/identity/specs?tag=productionGet a Specific Version
GET /v1/identity/specs/:versionReturns the full spec detail including raw_yaml, compiled_plan, and metadata. See the Rules & Specs API reference for complete request/response documentation.
Best Practices
Use semantic version strings. Name versions after the entity and increment: customer-v1, customer-v2. This makes API lookups and audit trails readable.
Check plan_hash in CI. Before deploying a spec change, compare the new hash against production. If the hash didn't change, the update is cosmetic and doesn't need re-reconciliation. See the CI/CD pipeline example in Spec Evolution.
Use metadata tags for filtering. Tag specs with production, staging, or experiment-* to organize versions:
metadata:
name: Customer Resolution
owner: data-team
tags: [production, customer]Then filter via API: GET /v1/identity/specs?tag=production.
Record plan_hash with every dataset. When you export golden records for downstream use (analytics, ML training), save the plan_hash alongside the data. This lets you trace any dataset back to the exact spec logic that produced it. See ML Training Data for the full pattern.
See Also
- Spec Evolution Tutorial — Step-by-step workflow: edit, validate, diff, CI/CD
- Rule Versioning — How individual rules are versioned independently of the spec
- Spec Reference — Complete YAML schema for all spec fields
