Skip to content

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}:

yaml
identity_version: customer-v3
entity: customer

sources:
  # ...

The identity_version is how you reference a specific spec via the API:

bash
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:

python
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:

IncludedNot Included
Entity typeSpec description
Source definitions and field mappingsMetadata name/owner
Blocking strategy and keysMetadata tags
Match rules, weights, algorithmsComments 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.

python
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

PropertyTypeDescription
rules_addedlist[str]Rule names present in new spec but not old
rules_removedlist[str]Rule names present in old spec but not new
rules_modifiedlist[dict]Rules in both specs with changed fields. Each dict: name, field, old_value, new_value
sources_addedlist[str]Source names added
sources_removedlist[str]Source names removed
sources_modifiedlist[dict]Sources with changed fields. Each dict: name, field, old_value, new_value
entity_changedboolWhether the entity definition changed
entity_changeslist[dict]Field-level entity changes. Each dict: path, old_value, new_value
blocking_changedboolWhether blocking strategy or keys changed
blocking_changeslist[dict]Field-level blocking changes
thresholds_changedboolWhether decision thresholds changed
decision_changeslist[dict]Field-level decision changes
survivorship_changedboolWhether survivorship rules changed
survivorship_changeslist[dict]Field-level survivorship changes
scoring_changedboolWhether scoring model configuration changed
scoring_changeslist[dict]Field-level scoring changes
metadata_changedboolWhether spec metadata changed
metadata_changeslist[dict]Field-level metadata changes
version_changedboolWhether identity_version changed
has_changesboolTrue if any part of the spec changed
summarystrHuman-readable summary of all changes

Example: Inspecting a Diff

python
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/specs
python
import 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']}")
bash
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:

json
{
  "valid": true,
  "warnings": [],
  "errors": [],
  "plan_hash": "a1b2c3d4e5f6..."
}

List All Spec Versions

GET /v1/identity/specs
GET /v1/identity/specs?tag=production

Get a Specific Version

GET /v1/identity/specs/:version

Returns 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:

yaml
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

The identity and delegation layer for AI agents.