# How Claim Assessments Are Validated in claude-obsidian: A Technical Deep Dive into Ledger Integrity

> Discover how claude-obsidian validates claim assessments using a rule-based workflow in ledgers.py. Learn about state constraints, evidence freshness, and risk-based independence.

- Repository: [Agrici.Daniel/claude-obsidian](https://github.com/AgriciDaniel/claude-obsidian)
- Tags: deep-dive
- Published: 2026-08-28

---

**Claim assessments in claude-obsidian are validated through a rigorous, rule-based workflow orchestrated by the `validate_claim_ledger` function in [`claude_obsidian/ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/ledgers.py), which enforces constraints on assessment states, evidence freshness, and risk-based independence requirements.**

The claude-obsidian project implements a sophisticated knowledge management system where claims must meet strict validity criteria before being accepted into the ledger. Understanding how **claim assessments are validated** in this repository requires examining the core validation engine that ensures data integrity through seven distinct verification stages. This system prevents logical contradictions and ensures high-risk assertions maintain proper sourcing standards.

## The Core Validation Entry Point

All claim assessment validation flows through the **`validate_claim_ledger`** function located in [`claude_obsidian/ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/ledgers.py) (lines 901‑915). This function accepts a claim ledger, a source ledger, and path parameters, returning a list of validation errors if any constraints are violated. The validator processes each claim entry against its associated evidence sources, applying assessment-specific rules that vary based on the claim's risk level and current assessment state.

## The Seven Critical Validation Rules

The validation engine applies a hierarchical series of checks that grow increasingly specific based on the claim's assessment classification.

### 1. Schema and Structure Verification

The validator first ensures the ledger conforms to the expected schema defined in [`claude_obsidian/contracts.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/contracts.py). Required fields—including `text`, `risk`, `assessment`, `confidence`, `location`, and `evidence`—must be present and correctly typed. This foundational check occurs at the function entry point (lines 901‑915) and prevents malformed data from proceeding to assessment-specific logic.

### 2. Assessment State Enumeration

The `assessment` field must contain exactly one of five predefined string constants: **`accepted`**, **`provisional`**, **`contested`**, **`unsupported`**, or **`deprecated`**. The validator enforces this enumeration at lines 58‑60, rejecting any claim with undefined assessment values before proceeding to state-specific constraints.

### 3. Temporal Constraints for Accepted Claims

Claims marked as `accepted` must carry a non-null `reviewed_at` timestamp, and this date cannot exceed the current validation time. The validator checks this constraint at lines 34‑40, ensuring that accepted claims represent reviews that have actually occurred rather than future-dated approvals.

### 4. Fresh Evidence Requirements for Acceptance

An `accepted` claim must possess at least one **fresh** supporting source (relation `"supports"`). Freshness requires multiple simultaneous conditions (lines 211‑225):

- The source origin must be valid (`_source_origin_is_valid`)
- The source's `review_status` equals `"active"`
- The source is not synthetic
- The source is not stale (`source_is_stale`)
- The source's retrieval/ingestion date is on or before the claim's review date

If no fresh supporting evidence exists, the validator raises an error at lines 226‑227, blocking acceptance of unsupported claims.

### 5. High-Risk Independence Constraints

When a claim carries `"high"` risk and an `accepted` assessment, the validator enforces an additional independence requirement (lines 228‑232). The system calculates independent source groups via `_independent_group_count` and requires **at least two independent groups** of supporting evidence. This prevents high-stakes claims from relying on a single origin point or closely related sources.

### 6. Contradiction Handling for Accepted Claims

If fresh contradictory evidence exists (relation `"contradicts"`) for an `accepted` claim, the validator demands either:
- A `contested` assessment status, or
- Non-empty `notes` providing adjudication context

This check at lines 236‑242 prevents silently accepted claims from coexisting with known contradictory evidence without explicit documentation.

### 7. Contested Claim Justification

Claims explicitly marked as `contested` must satisfy at least one of two conditions (lines 244‑250):
- The claim references at least one contradicting source in its evidence array, or
- The claim includes non-empty `notes` explaining the contestation

This ensures that contested status represents an active, documented dispute rather than an arbitrary label.

## Practical Implementation: Code Examples

The following example demonstrates a valid high-risk accepted claim with proper supporting evidence:

```python
from pathlib import Path
from claude_obsidian.ledgers import validate_claim_ledger, empty_claim_ledger, empty_source_ledger

# 1️⃣ Build a minimal source ledger with an active, non‑synthetic source

source_ledger = empty_source_ledger()
source_ledger["sources"]["src-1234567890abcdef"] = {
    "origin": {"kind": "url", "locator": "https://example.com/article"},
    "content_kind": "document",
    "title": "Example article",
    "authority": "official",
    "content_sha256": "a"*64,
    "review_status": "active",
    "authority": "official",
    "content_kind": "document",
    "ingested_at": "2023-01-01",
    "retrieved_at": "2023-01-01",
    "refresh_due": "2024-01-01",
    "pages": ["wiki/example.md"],
    "independence_key": None,
    "supersedes": None,
}

# 2️⃣ Create a claim that is accepted and high‑risk

claim_ledger = empty_claim_ledger()
claim_ledger["claims"]["clm-001"] = {
    "text": "The algorithm reduces carbon emissions by 20 %.",
    "risk": "high",
    "assessment": "accepted",
    "confidence": "high",
    "location": {"path": "wiki/claims.md", "anchor": None},
    "reviewed_at": "2023-02-01",
    "notes": "Reviewed by senior researcher.",
    "evidence": [
        {
            "source_id": "src-1234567890abcdef",
            "relation": "supports",
            "locator": None,
        }
    ],
    "supersedes": None,
}

# 3️⃣ Run the validator – it will raise no errors if all constraints are met

errors = validate_claim_ledger(claim_ledger, source_ledger, as_of=Path("."), vault_root=Path("."))
assert not errors  # passes validation

```

This example triggers a validation error by adding contradictory evidence without changing the assessment status or adding notes:

```python

# Example that triggers a validation error:

claim_ledger["claims"]["clm-001"]["assessment"] = "accepted"
claim_ledger["claims"]["clm-001"]["evidence"].append({
    "source_id": "src-1234567890abcdef",
    "relation": "contradicts",   # fresh contradictory source

    "locator": None,
})

# No notes provided → error

errors = validate_claim_ledger(claim_ledger, source_ledger, as_of=Path("."), vault_root=Path("."))
print(errors)  # contains “accepted claims with fresh contradictory evidence require contested assessment or adjudication notes”

```

## Key Source Files and Architecture

The validation system spans several critical components within the repository:

- **[`claude_obsidian/ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/ledgers.py)** – Houses the `validate_claim_ledger` function and all assessment-specific validation logic, including the fresh evidence checker and independence calculator.

- **[`claude_obsidian/contracts.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/contracts.py)** – Defines the JSON Schema constants (`CLAIM_SCHEMA`) that govern the structural requirements for claim ledgers and their associated metadata.

- **[`tests/test_ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/tests/test_ledgers.py)** – Contains comprehensive unit tests verifying claim assessment validation, including edge cases for high-risk acceptance, contradiction handling, and contested state transitions.

Assessment states like `provisional`, `unsupported`, and `deprecated` undergo only the generic field validation described in Rule 1 and Rule 2, carrying no additional constraints regarding evidence freshness or contradiction handling.

## Summary

- The **`validate_claim_ledger`** function in [`claude_obsidian/ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/ledgers.py) serves as the central validation authority for all claim assessments.
- **Five assessment states** are recognized (`accepted`, `provisional`, `contested`, `unsupported`, `deprecated`), with `accepted` and `contested` carrying the strictest additional requirements.
- **Accepted claims** must possess fresh supporting evidence, a valid review date, and—if high-risk—evidence from at least two independent source groups.
- **Contradictory evidence** forces either a `contested` status or explicit adjudication notes for accepted claims.
- **Contested claims** require either contradictory evidence documentation or explanatory notes to satisfy validation.

## Frequently Asked Questions

### What happens if an accepted claim lacks fresh supporting evidence?

The validator returns an error indicating that accepted claims require fresh active support. According to lines 226‑227 in [`claude_obsidian/ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/ledgers.py), the absence of fresh supporting sources (those that are active, non-synthetic, non-stale, and properly dated) causes immediate validation failure, preventing unsupported claims from entering the accepted state.

### Can a high-risk claim be accepted with only one independent source?

No. Lines 228‑232 in [`claude_obsidian/ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/ledgers.py) enforce a strict requirement that high-risk accepted claims must have at least two independent groups of supporting evidence. The validator calculates independence via `_independent_group_count` and rejects high-risk single-source acceptances to prevent over-reliance on limited provenance.

### Which assessment states require additional validation beyond basic structure?

Only **`accepted`** and **`contested`** assessments trigger additional constraint checks. The states `provisional`, `unsupported`, and `deprecated` undergo only schema validation and assessment value enumeration. Accepted claims face requirements for fresh evidence, review dates, independence (if high-risk), and contradiction handling, while contested claims must provide either contradictory evidence or explanatory notes.

### How does the system define "fresh" evidence for claim validation?

Fresh evidence, as implemented in lines 211‑225, requires that a supporting source maintains `"active"` review status, possesses a valid origin, is not flagged as synthetic, passes the staleness check (`source_is_stale`), and has retrieval/ingestion dates on or before the claim's own review date. Sources failing any of these criteria are excluded from the fresh support calculation, potentially blocking claim acceptance.