Audit Logging & Match Trail
Capture every match decision, rule trace, and conflict resolution for regulatory compliance and debugging.
Overview
Identity resolution produces complex decisions: which records matched, which rules fired, what confidence scores were assigned, and how conflicts were resolved. Kanoniv's audit logging captures this full decision trail so you can explain any match after the fact.
Cloud Feature
Audit configuration in the spec requires the Cloud tier. Basic audit events (API actions, overrides) are available on all tiers.
Spec Configuration
Add an audit block to your decision section:
decision:
thresholds:
match: 0.9
review: 0.7
conflict_strategy: prefer_high_confidence
audit:
log_all_comparisons: true
log_decisions: true
log_conflicts: trueFields
| Field | Type | Default | Description |
|---|---|---|---|
log_all_comparisons | bool | false | Log every pairwise comparison, including NoMerge (non-match) decisions. Must be explicitly enabled. |
log_decisions | bool | true | Log Merge and Review decisions with scores. Enabled by default; set to false to suppress. |
log_conflicts | bool | false | Log conflict resolution details when multiple matches compete (survivorship conflicts) |
Default Behavior
Even with no audit block in your spec, log_decisions defaults to true, so Merge and Review decisions are always audited out of the box. Only NoMerge decisions require explicit opt-in via log_all_comparisons. When compliance.audit_required is true, all decisions (including NoMerge) are force-audited regardless of these flags.
Verbosity Levels
Choose a verbosity level based on your needs:
Minimal (Decisions Only), Default
This is the default behavior even without an audit block. Merge and Review decisions are logged automatically. If you want to be explicit:
decision:
audit:
log_decisions: true # default: true (can be omitted)To disable decision logging entirely:
decision:
audit:
log_decisions: falseStandard (Decisions + Conflicts)
Good for debugging match quality and understanding why certain records were grouped:
decision:
audit:
log_decisions: true
log_conflicts: trueFull (All Comparisons)
Captures every comparison the engine evaluates. Useful for compliance audits and ML training data, but generates significantly more data:
decision:
audit:
log_all_comparisons: true
log_decisions: true
log_conflicts: trueWARNING
Enabling log_all_comparisons can produce large volumes of audit data. For a dataset with 100K records using composite blocking, this could generate millions of comparison logs. Use selectively or pair with a retention policy.
What Gets Logged
Match Decisions
Every match decision includes:
| Field | Description |
|---|---|
entity_a_id | First entity in the comparison |
entity_b_id | Second entity in the comparison |
decision | match, reject, or review |
confidence | Final confidence score (0.0 - 1.0) |
rule_trace | Which rules fired and their individual scores |
blocking_key | The blocking key that brought these records together |
Rule Traces
Each rule trace entry shows:
{
"rule_name": "email_exact",
"rule_type": "exact",
"field": "email",
"result": true,
"score": 1.0,
"weight": 1.0
}Conflict Logs
When multiple candidate matches compete for the same entity:
{
"entity_id": "ent_001",
"candidates": [
{ "match_id": "ent_042", "confidence": 0.95 },
{ "match_id": "ent_078", "confidence": 0.91 }
],
"strategy": "prefer_high_confidence",
"winner": "ent_042"
}PII-Safe Audit Trail
When compliance is configured, audit logs automatically redact PII:
- Blocking keys derived from PII fields are hashed
- Rule traces show field names but not raw values
- Entity IDs are preserved (they are system-generated, not PII)
This ensures audit logs can be shared with compliance teams and stored in external systems without exposing sensitive data.
Querying Audit Logs
Match Audit Records
import httpx
# Query match audit records for a specific batch
resp = httpx.get(
"https://api.kanoniv.com/v1/audit/matches",
params={"batch_run_id": "run_abc123", "decision": "match", "limit": 50},
headers={"X-API-Key": "kn_..."},
)
audits = resp.json()
for audit in audits:
print(f"{audit['entity_a_id']} ↔ {audit['entity_b_id']}: "
f"{audit['decision']} ({audit['confidence']:.2f})")
for rule in audit["rule_trace"]:
print(f" - {rule['rule_name']}: {rule['score']}")# Query match audits for a batch run
curl "https://api.kanoniv.com/v1/audit/matches?batch_run_id=run_abc123&decision=match&limit=50" \
-H "X-API-Key: kn_..."
# Response:
# [
# {
# "entity_a_id": "...",
# "entity_b_id": "...",
# "decision": "match",
# "confidence": 0.95,
# "rule_trace": [...],
# "blocking_key": "smith|j"
# }
# ]General Audit Events
import httpx
# Query general audit events (API actions, overrides, etc.)
resp = httpx.get(
"https://api.kanoniv.com/v1/audit",
params={"event_type": "override.create", "limit": 100},
headers={"X-API-Key": "kn_..."},
)
events = resp.json()
for event in events:
print(f"{event['timestamp']} - {event['action']} by {event['actor_id']}")curl "https://api.kanoniv.com/v1/audit?event_type=override.create&limit=100" \
-H "X-API-Key: kn_..."Full Example
api_version: kanoniv/v2
identity_version: customer_v3
entity:
name: customer
compliance:
frameworks:
- soc2
pii_fields:
- email
- phone
audit_required: true
sources:
- name: crm
adapter: postgres
location: public.contacts
primary_key: contact_id
attributes:
email: email_address
phone: phone_number
name: full_name
rules:
- type: exact
name: email_match
field: email
weight: 1.0
- type: similarity
name: name_match
field: name
algorithm: jaro_winkler
threshold: 0.85
weight: 0.7
decision:
thresholds:
match: 0.9
review: 0.7
conflict_strategy: prefer_high_confidence
audit:
log_all_comparisons: false
log_decisions: true
log_conflicts: trueSIEM Integration
Audit events can be streamed to external security monitoring tools in real time. See SIEM Integration for configuring destinations like Splunk, S3, or Kafka.
Next Steps
- Compliance & PII Masking: Declare frameworks and protect sensitive data
- SIEM Integration: Stream audit events to external tools
- Spec Governance: Understand the full spec lifecycle
