Skip to content

Add Evidence–Claim Divergence (ECD) estimator, docs, and tests - #239

Open
Tarik Skalić (tarikskalic33) wants to merge 1 commit into
mainfrom
codex/introduce-evidenceclaim-divergence-framework
Open

Add Evidence–Claim Divergence (ECD) estimator, docs, and tests#239
Tarik Skalić (tarikskalic33) wants to merge 1 commit into
mainfrom
codex/introduce-evidenceclaim-divergence-framework

Conversation

@tarikskalic33

Copy link
Copy Markdown
Member

Motivation

  • Provide a small, deterministic reference implementation of the Evidence–Claim Divergence / Hallucination Distance concept so the paper-level proposal has a runnable T2 research instrument.
  • Enable reproducible witness generation and tamper-evident measurement records that integrate with the existing AEGIS-Ω LineageChain for deterministic auditing.
  • Surface a clear developer-facing spec and protocol (ERB) so downstream experiments can evaluate sensitivity, monotonicity, repeatability, and observer invariance.

Description

  • Add verifiable/ecd.py, a stdlib-only, deterministic estimator implementing Claim and EvidenceNode dataclasses, weighted HD components (execution, omission, unsupported, contradiction, calibration), exact rational scoring via Fraction, hd_delta (tick-based), and measurement_chain that emits a LineageChain witness.
  • Add verifiable/test_ecd.py, a small deterministic test harness that checks perfect alignment → zero HD, increases under unsupported/contradictory claims, reproducible/tamper-evident measurement chains, and positive hd_delta for divergence.
  • Add docs/EVIDENCE_CLAIM_DIVERGENCE.md, a user-facing document describing the formal objects (C, E), the reference HD estimator, ERB tracks, temporal dynamics, evidence quality reporting, metrological desiderata, and limitations.
  • Update verifiable/README.md to list the new estimator and tests so the verifiable substrate documents the added research instrument.

Testing

  • Ran the estimator checks with python3 verifiable/test_ecd.py, which completed successfully (all tests passed).
  • Verified substrate generality and cross-runtime anchoring with python3 verifiable/test_generality.py, which completed successfully.
  • Performed a static syntax/compile check with python3 -m py_compile verifiable/ecd.py verifiable/test_ecd.py, which succeeded with no syntax errors.

Codex Task

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hook-generator Ready Ready Preview, Comment Jul 28, 2026 3:58pm
hub Ready Ready Preview, Comment Jul 28, 2026 3:58pm
platform-picker Ready Ready Preview, Comment Jul 28, 2026 3:58pm

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
aegisomega 93d4204 Commit Preview URL

Branch Preview URL
Jul 28 2026, 03:59 PM

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add deterministic Evidence–Claim Divergence estimator with docs and tests

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Introduce a stdlib-only Evidence–Claim Divergence / Hallucination Distance estimator with exact
 rational scoring.
• Emit tamper-evident, reproducible measurement witnesses via the existing LineageChain.
• Add a formal developer-facing spec plus deterministic tests covering alignment, divergence, and
 delta-by-ticks.
Diagram

graph TD
DOC["docs/EVIDENCE_CLAIM_DIVERGENCE.md"] --> ECD["verifiable/ecd.py"] --> CHAIN["verifiable/chain.py"]
TEST["verifiable/test_ecd.py"] --> ECD
README["verifiable/README.md"] --> ECD
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Pluggable distance/mapping interface
  • ➕ Makes it easier to experiment with different claim→graph mappings and graph distances without editing the reference estimator
  • ➕ Supports multiple HD-family estimators behind a stable protocol boundary
  • ➖ More abstraction and surface area than needed for a T2 reference implementation
  • ➖ Harder to keep the implementation obviously deterministic and dependency-free
2. Fold evidence quality into HD (single scalar)
  • ➕ Produces one headline number (simplifies downstream ranking/thresholding)
  • ➖ Conflates instrumentation quality with divergence signal; can hide whether HD changed due to poorer evidence vs worse claims
  • ➖ Harder to compare runs where Q_evidence differs
3. Property-based testing for invariants
  • ➕ Can systematically validate monotonicity/sensitivity expectations across many generated scenarios
  • ➖ Typically introduces non-stdlib dependencies (e.g., Hypothesis) unless custom-built
  • ➖ Can complicate determinism guarantees if not carefully constrained

Recommendation: Keep the current stdlib-only, deterministic reference approach: it matches the stated goal (reproducible T2 research instrument) and integrates cleanly with LineageChain for tamper-evident witnesses. If/when multiple estimators or graph distances become necessary, introduce a thin plug-in interface around estimate_hd while preserving the exact rational core and deterministic serialization contract.

Files changed (4) +321 / -0

Enhancement (1) +162 / -0
ecd.pyAdd deterministic ECD / Hallucination Distance reference estimator +162/-0

Add deterministic ECD / Hallucination Distance reference estimator

• Adds dataclasses for 'Claim', 'EvidenceNode', weights, and an 'HDResult' with exact 'Fraction'-based scoring. Implements 'estimate_hd' (execution mismatch, omission, unsupported, contradiction, calibration), 'hd_delta' (tick-based), and 'measurement_chain' to produce a 'LineageChain' witness payload.

verifiable/ecd.py

Tests (1) +57 / -0
test_ecd.pyAdd deterministic tests for HD behavior and witness reproducibility +57/-0

Add deterministic tests for HD behavior and witness reproducibility

• Adds a small, dependency-free test runner validating: perfect alignment yields zero HD, divergence increases HD (unsupported + contradiction), measurement chains are reproducible and tamper-evident, and 'hd_delta' is positive under divergence. Uses local 'sys.path' injection consistent with other verifiable tests.

verifiable/test_ecd.py

Documentation (2) +102 / -0
EVIDENCE_CLAIM_DIVERGENCE.mdAdd formal ECD/HD specification and ERB protocol doc +100/-0

Add formal ECD/HD specification and ERB protocol doc

• Introduces a user-facing specification for Evidence–Claim Divergence and the reference Hallucination Distance estimator. Documents metric components, evidence quality reporting, tick-based HD delta, ERB benchmark tracks, and scope/limitations.

docs/EVIDENCE_CLAIM_DIVERGENCE.md

README.mdDocument new ECD estimator and test harness +2/-0

Document new ECD estimator and test harness

• Extends the verifiable substrate index to include 'ecd.py' and 'test_ecd.py'. Provides a brief description of the estimator and the deterministic checks it supports.

verifiable/README.md

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 11 rules

Grey Divider


Action required

1. Unchecked weight_total division 🐞 Bug ≡ Correctness
Description
In estimate_hd(), caller-supplied HDWeights can sum to 0 (or include negative values), causing a
ZeroDivisionError or semantically invalid/negative HD output.
This makes the estimator fragile for any downstream experiment that tunes weights.
Code

verifiable/ecd.py[R141-148]

+    weight_total = weights.execution + weights.omission + weights.unsupported + weights.contradiction + weights.calibration
+    hd = (
+        weights.execution * execution
+        + weights.omission * omission
+        + weights.unsupported * unsupported_rate
+        + weights.contradiction * contradiction
+        + weights.calibration * calibration
+    ) / weight_total
Relevance

●● Moderate

Reliability hardening aligns with repo direction (PR #217/#219); no specific evidence on
weight_total validation.

PR-#217
PR-#219

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code computes weight_total from user-provided weights and then divides by it without any
guard, so a zero sum deterministically triggers division by zero.

verifiable/ecd.py[39-45]
verifiable/ecd.py[141-148]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`estimate_hd()` divides by `weight_total` without validating it. If weights sum to 0, the function raises `ZeroDivisionError`; if weights are negative, the returned HD can be negative or otherwise outside the intended scale.

### Issue Context
Weights are part of the public API and are likely to be tuned by downstream users.

### Fix Focus Areas
- verifiable/ecd.py[39-45]
- verifiable/ecd.py[141-148]

### Suggested fix
- Add validation (either in `HDWeights.__post_init__()` or at the start of `estimate_hd()`):
 - require all weights to be non-negative integers (or at least `>= 0`)
 - require `weight_total > 0`
 - raise `ValueError` with a clear message if invalid.
- (Optional) Add a small test that passing all-zero weights raises `ValueError` (instead of crashing with `ZeroDivisionError`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Duplicate evidence IDs overwrite 🐞 Bug ☼ Reliability
Description
estimate_hd() silently overwrites earlier EvidenceNode entries when multiple nodes share the same
evidence_id, which can change which evidence is treated as "supporting" and therefore change HD
results.
Because ties are not rejected, results can become sensitive to the iteration order of malformed
inputs (e.g., when evidence is supplied from an unordered iterable) despite the module’s
deterministic intent.
Code

verifiable/ecd.py[R90-106]

+    claim_list = sorted(claims, key=lambda c: c.claim_id)
+    evidence_list = sorted(evidence, key=lambda e: e.evidence_id)
+    total_claims = len(claim_list)
+    total_evidence = len(evidence_list)
+    verified_evidence = sum(1 for node in evidence_list if node.verified)
+
+    if total_claims == 0:
+        zero = Fraction(0, 1)
+        quality = Fraction(verified_evidence, total_evidence) if total_evidence else zero
+        return HDResult(zero, zero, zero, zero, zero, zero, quality, verified_evidence, total_evidence)
+
+    evidence_by_id = {node.evidence_id: node for node in evidence_list}
+    verified_by_key: dict[tuple[str, str], list[EvidenceNode]] = {}
+    for node in evidence_list:
+        if node.verified:
+            verified_by_key.setdefault(_evidence_key(node), []).append(node)
+
Relevance

●● Moderate

Repo values determinism/fail-closed (PR #217/#219), but no history on duplicate-id collision checks.

PR-#217
PR-#219

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The module emphasizes determinism, yet it builds a dict keyed by evidence_id, which necessarily
discards earlier nodes on key collisions (silent overwrite).

verifiable/ecd.py[5-8]
verifiable/ecd.py[90-106]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`evidence_by_id` is built with a dict comprehension keyed by `evidence_id`, so duplicates are silently dropped (last one wins). This is a data-integrity hazard and can make outputs depend on which duplicate happens to be retained.

### Issue Context
The module explicitly positions itself as a deterministic reference estimator, so ambiguous identifiers should be rejected early and loudly.

### Fix Focus Areas
- verifiable/ecd.py[5-8]
- verifiable/ecd.py[90-106]

### Suggested fix
- Validate uniqueness:
 - after sorting, scan `claim_list` for duplicate `claim_id` and raise `ValueError` if found
 - scan `evidence_list` for duplicate `evidence_id` and raise `ValueError` if found
- (Alternative) If duplicates are intended, change `evidence_by_id` to map IDs to a list and define a deterministic selection rule; but rejection is usually better for a measurement substrate.
- Add a targeted test: duplicate evidence IDs should raise `ValueError`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Out-of-range confidence accepted 🐞 Bug ≡ Correctness
Description
estimate_hd() converts Claim.confidence_ppm directly into a Fraction with no bounds checks, so
negative values or values > 1_000_000 produce expressed confidence outside [0,1].
This can inflate calibration_mismatch (and overall HD) beyond the intended normalized scale, making
results hard to interpret and compare.
Code

verifiable/ecd.py[R126-128]

+        observed_correct = Fraction(1, 1) if matching else Fraction(0, 1)
+        expressed = Fraction(claim.confidence_ppm, 1_000_000)
+        calibration_error += abs(expressed - observed_correct)
Relevance

●● Moderate

Fail-closed/malformed input hardening accepted (PR #217/#219); no precedent for ppm confidence
bounds enforcement.

PR-#217
PR-#219

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Claim dataclass allows any int for confidence_ppm, and estimate_hd uses it directly as a
numerator in a probability Fraction, so invalid inputs propagate straight into calibration_error.

verifiable/ecd.py[19-27]
verifiable/ecd.py[126-128]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`Claim.confidence_ppm` is treated as parts-per-million probability, but it is not validated. Out-of-range values yield expressed confidence outside [0,1], distorting calibration and HD.

### Issue Context
The docs and code use PPM as a deterministic probability representation; that implies a fixed domain of valid values.

### Fix Focus Areas
- verifiable/ecd.py[19-27]
- verifiable/ecd.py[126-128]

### Suggested fix
- Add validation (preferably in `Claim.__post_init__()`):
 - require `0 <= confidence_ppm <= 1_000_000`
 - raise `ValueError` on violation
- Add a small test case that constructing a claim with `confidence_ppm=-1` or `1_000_001` raises `ValueError`.
- (If you explicitly want to allow out-of-range), document that behavior and its impact on calibration/HD scaling.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread verifiable/ecd.py
Comment on lines +141 to +148
weight_total = weights.execution + weights.omission + weights.unsupported + weights.contradiction + weights.calibration
hd = (
weights.execution * execution
+ weights.omission * omission
+ weights.unsupported * unsupported_rate
+ weights.contradiction * contradiction
+ weights.calibration * calibration
) / weight_total

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Unchecked weight_total division 🐞 Bug ≡ Correctness

In estimate_hd(), caller-supplied HDWeights can sum to 0 (or include negative values), causing a
ZeroDivisionError or semantically invalid/negative HD output.
This makes the estimator fragile for any downstream experiment that tunes weights.
Agent Prompt
### Issue description
`estimate_hd()` divides by `weight_total` without validating it. If weights sum to 0, the function raises `ZeroDivisionError`; if weights are negative, the returned HD can be negative or otherwise outside the intended scale.

### Issue Context
Weights are part of the public API and are likely to be tuned by downstream users.

### Fix Focus Areas
- verifiable/ecd.py[39-45]
- verifiable/ecd.py[141-148]

### Suggested fix
- Add validation (either in `HDWeights.__post_init__()` or at the start of `estimate_hd()`):
  - require all weights to be non-negative integers (or at least `>= 0`)
  - require `weight_total > 0`
  - raise `ValueError` with a clear message if invalid.
- (Optional) Add a small test that passing all-zero weights raises `ValueError` (instead of crashing with `ZeroDivisionError`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread verifiable/ecd.py
Comment on lines +90 to +106
claim_list = sorted(claims, key=lambda c: c.claim_id)
evidence_list = sorted(evidence, key=lambda e: e.evidence_id)
total_claims = len(claim_list)
total_evidence = len(evidence_list)
verified_evidence = sum(1 for node in evidence_list if node.verified)

if total_claims == 0:
zero = Fraction(0, 1)
quality = Fraction(verified_evidence, total_evidence) if total_evidence else zero
return HDResult(zero, zero, zero, zero, zero, zero, quality, verified_evidence, total_evidence)

evidence_by_id = {node.evidence_id: node for node in evidence_list}
verified_by_key: dict[tuple[str, str], list[EvidenceNode]] = {}
for node in evidence_list:
if node.verified:
verified_by_key.setdefault(_evidence_key(node), []).append(node)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Duplicate evidence ids overwrite 🐞 Bug ☼ Reliability

estimate_hd() silently overwrites earlier EvidenceNode entries when multiple nodes share the same
evidence_id, which can change which evidence is treated as "supporting" and therefore change HD
results.
Because ties are not rejected, results can become sensitive to the iteration order of malformed
inputs (e.g., when evidence is supplied from an unordered iterable) despite the module’s
deterministic intent.
Agent Prompt
### Issue description
`evidence_by_id` is built with a dict comprehension keyed by `evidence_id`, so duplicates are silently dropped (last one wins). This is a data-integrity hazard and can make outputs depend on which duplicate happens to be retained.

### Issue Context
The module explicitly positions itself as a deterministic reference estimator, so ambiguous identifiers should be rejected early and loudly.

### Fix Focus Areas
- verifiable/ecd.py[5-8]
- verifiable/ecd.py[90-106]

### Suggested fix
- Validate uniqueness:
  - after sorting, scan `claim_list` for duplicate `claim_id` and raise `ValueError` if found
  - scan `evidence_list` for duplicate `evidence_id` and raise `ValueError` if found
- (Alternative) If duplicates are intended, change `evidence_by_id` to map IDs to a list and define a deterministic selection rule; but rejection is usually better for a measurement substrate.
- Add a targeted test: duplicate evidence IDs should raise `ValueError`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread verifiable/ecd.py
Comment on lines +126 to +128
observed_correct = Fraction(1, 1) if matching else Fraction(0, 1)
expressed = Fraction(claim.confidence_ppm, 1_000_000)
calibration_error += abs(expressed - observed_correct)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Out-of-range confidence accepted 🐞 Bug ≡ Correctness

estimate_hd() converts Claim.confidence_ppm directly into a Fraction with no bounds checks, so
negative values or values > 1_000_000 produce expressed confidence outside [0,1].
This can inflate calibration_mismatch (and overall HD) beyond the intended normalized scale, making
results hard to interpret and compare.
Agent Prompt
### Issue description
`Claim.confidence_ppm` is treated as parts-per-million probability, but it is not validated. Out-of-range values yield expressed confidence outside [0,1], distorting calibration and HD.

### Issue Context
The docs and code use PPM as a deterministic probability representation; that implies a fixed domain of valid values.

### Fix Focus Areas
- verifiable/ecd.py[19-27]
- verifiable/ecd.py[126-128]

### Suggested fix
- Add validation (preferably in `Claim.__post_init__()`):
  - require `0 <= confidence_ppm <= 1_000_000`
  - raise `ValueError` on violation
- Add a small test case that constructing a claim with `confidence_ppm=-1` or `1_000_001` raises `ValueError`.
- (If you explicitly want to allow out-of-range), document that behavior and its impact on calibration/HD scaling.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 93d420468c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread verifiable/ecd.py
Comment on lines +121 to +122
elif not supporting:
unsupported += 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Count mixed bad citations as unsupported

When a claim cites both one valid verified evidence id and another missing or unverified id, supporting is non-empty, so this branch leaves unsupported_assertions at zero and the HD can still report perfect alignment. The new estimator docs define A_unsupported as a claim citing evidence that is missing or unverified (docs/EVIDENCE_CLAIM_DIVERGENCE.md:41), so a bad lineage entry can currently be hidden just by appending any good evidence id; check each cited id rather than only whether any support exists.

Useful? React with 👍 / 👎.

Comment thread verifiable/README.md
| `compliance_pipeline.py` | A **regulated decision-audit** pipeline (`INTAKE → EXTRACT → SCORE → DECISION`) — AEGIS's stated market: EU AI Act Article 12 tamper-evident decision records. Integer scorecard, adverse-action reason codes, integer threshold. |
| `test_generality.py` | The proof. Exit 0 = all four claims hold. |
| `ecd.py` | Deterministic reference estimator for Evidence–Claim Divergence / Hallucination Distance over claims and evidence nodes. |
| `test_ecd.py` | Estimator checks for alignment, unsupported claims, contradiction, deterministic witness hashing, tamper evidence, and Hallucination Delta. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wire the ECD checks into CI

This adds test_ecd.py as the estimator check suite, but I checked the verifiable workflow (.github/workflows/verifiable-proofs.yml:50-87) and the session certifier proof list (verifiable/certify_all.py:41-47), and neither invokes it. As a result, PRs changing verifiable/ecd.py still get a green CI/session certificate even if these new checks fail; please add python3 test_ecd.py to the workflow and PROOFS list, updating the pinned session certificate as needed.

Useful? React with 👍 / 👎.

Comment thread verifiable/ecd.py


def ppm(x: Fraction) -> int:
return int(x * 1_000_000)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve sub-ppm HD in witnesses

For ERB-sized traces with more than 1,000,000 weighted opportunities, a real non-zero divergence below one ppm is floored to 0 here before measurement_chain() hashes as_payload(). That lets distinct exact Fraction results, including clean versus one tiny mismatch, produce identical witness payloads despite the estimator keeping exact rationals; serialize numerator/denominator pairs or another lossless form for chain payloads.

Useful? React with 👍 / 👎.

Comment thread verifiable/ecd.py
execution_mismatches += 1

observed_correct = Fraction(1, 1) if matching else Fraction(0, 1)
expressed = Fraction(claim.confidence_ppm, 1_000_000)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject confidence values outside ppm range

When a caller supplies an impossible confidence_ppm such as 1_500_000 or a negative value, this code treats it as a valid confidence fraction instead of rejecting it. Since the docs define confidence as integer parts-per-million, out-of-range inputs can create calibration mismatches above 1 or penalize a fully supported claim even though the claim/evidence pair is aligned; validate 0 <= confidence_ppm <= 1_000_000 before using it in the estimator.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant