What invariants prevent confidence laundering across verifier chains?
**Date of Record:** 2026 October 27 **Scope:** Applies when a claim's confidence is passed through a chain of verifiers (A verifies B's claim, C verifies A's verification, D verifies C's verification), and the confidence signal must not inflate across the chain even though each verifier "agrees." Does not apply to flat verification structures or claims with cryptographic confidence bounds.
Core Distinction
The separation between **agreement through chains** (each verifier confirms the previous) and **confidence escalation** (the aggregate confidence exceeds what any single verification justifies). Relying on chains creates the **Confidence Laundering**: verifier A says "70% confident," verifier B says "I'm 90% confident A's verification is valid," and the chain aggregate reports 63% or even 90% confidence, when the actual confidence is bounded by the weakest link (70% in this case).
What Was Observed
In verifier-chain agent architectures, failing to prevent confidence inflation produces three primary failure modes:
- **Chain-Product Error:** The chain's confidence is computed as a product of per-link confidence. This under-reports when links are highly correlated, and over-reports when some links use confidence-favorable criteria.
- **Attribution Escalation:** Each verifier reports confidence in its own verification, not the underlying claim. The chain's aggregate confidence becomes "confidence that all verifiers agreed," not "confidence in the claim."
- **Authority Multiplication:** A high-authority verifier's confidence multiplies with a low-authority verifier's confidence, creating an authority-weighted number that has no statistical interpretation.
Recorded Elements
- **`per_verifier_confidence_receipt`:** For each verifier, its stated confidence in the underlying claim (not in the previous verifier), so confidence inflation can be detected.
- **`verifier_confidence_ceiling_signature`:** The maximum confidence any verifier in the chain can contribute, bounded by the weakest verifier's confidence.
- **`chain_confidence_aggregation_method_signature`:** The exact method used to aggregate confidence across the chain, so the receiver can verify the aggregation is not inflating.
- **`chain_confidence_recomputation_receipt`:** The receiver's independent recomputation of chain confidence, validated against the reported chain confidence.
Condition of Validity
* **Applies when:** Confidence is passed through verifier chains, chain length can be >1, and confidence inflation would mislead the receiver. * **Does not apply when:** All verifications are independent (confidence aggregates properly) or verification is binary (confident/not).
Decision Boundary
- **Does the claim's confidence pass through a verifier chain?** If no, single-verifier confidence applies. If yes, proceed.
- **Is the chain's reported confidence greater than the minimum per-verifier confidence?** If yes, the chain has inflated confidence.
- **Is the `chain_confidence_aggregation_method_signature` a sound statistical method?** If yes, accept the aggregation. If no, require independent recomputation.
- **Conversely, when can chain confidence exceed the weakest link?** Only when verifiers are provably independent (not correlated), so independent confirmations provide genuine additional evidence.
Operational Effect
- **Confidence Inflation Prevention:** Chain confidence is bounded by the weakest link (for correlated verifiers).
- **Statistical Soundness:** Aggregation methods are verified, not just reported.
- **Independent Recomputation:** Receivers can verify confidence rather than trust it.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class ConfidenceVerdict(Enum):
SINGLE_VERIFIER = auto()
CHAIN_SOUND = auto()
CHAIN_INFLATED = auto()
UNKNOWN_AGGREGATION = auto()
@dataclass(frozen=True)
class VerifierLink:
verifier_id: str
confidence_in_claim: float
confidence_in_previous_verifier: float
is_independent: bool
@dataclass(frozen=True)
class VerifierChain:
chain_id: str
links: tuple[VerifierLink, ...]
reported_chain_confidence: float
aggregation_method: str # "MIN" | "INDEPENDENT_PRODUCT" | "NAIVE_PRODUCT" | "AVERAGE"
@dataclass(frozen=True)
class ConfidencePolicy:
require_independence_for_product: bool
@dataclass(frozen=True)
class ConfidenceManifest:
verdict: ConfidenceVerdict
recomputed_confidence: float
directive: str
manifest_digest: str
class ConfidenceLaunderingGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_chain(
cls, chain: VerifierChain, policy: ConfidencePolicy
) -> ConfidenceManifest:
# Gate 1: Single verifier
if len(chain.links) <= 1:
c = chain.links[0].confidence_in_claim if chain.links else 0.0
digest = cls._hash(json.dumps({
"chain": chain.chain_id,
"confidence": f"{c:.3f}"
}, sort_keys=True))
return ConfidenceManifest(
verdict=ConfidenceVerdict.SINGLE_VERIFIER,
recomputed_confidence=c,
directive=f"PROCEED: Single verifier confidence {c:.2f}.",
manifest_digest=digest
)
# Gate 2: Independence check for product-based methods
if chain.aggregation_method in ("INDEPENDENT_PRODUCT", "NAIVE_PRODUCT"):
if policy.require_independence_for_product:
if not all(link.is_independent for link in chain.links):
return ConfidenceManifest(
verdict=ConfidenceVerdict.CHAIN_INFLATED,
recomputed_confidence=0.0,
directive=f"HALT: Product aggregation requires independence, some links not independent.",
manifest_digest=cls._hash(f"NOT_INDEPENDENT:{chain.chain_id}")
)
# Gate 3: Recompute based on method
if chain.aggregation_method == "MIN":
recomputed = min(link.confidence_in_claim for link in chain.links)
elif chain.aggregation_method == "INDEPENDENT_PRODUCT":
recomputed = 1.0
for link in chain.links:
recomputed *= link.confidence_in_claim
elif chain.aggregation_method == "NAIVE_PRODUCT":
# Naive product on non-independent links over-reports; treat as suspicious
recomputed = min(link.confidence_in_claim for link in chain.links)
elif chain.aggregation_method == "AVERAGE":
# Averaging is also problematic but less inflating than product
recomputed = sum(link.confidence_in_claim for link in chain.links) / len(chain.links)
else:
return ConfidenceManifest(
verdict=ConfidenceVerdict.UNKNOWN_AGGREGATION,
recomputed_confidence=0.0,
directive=f"HALT: Unknown aggregation method {chain.aggregation_method}.",
manifest_digest=cls._hash(f"UNKNOWN:{chain.chain_id}")
)
# Gate 4: Compare reported vs recomputed
if chain.reported_chain_confidence > recomputed + 0.01:
return ConfidenceManifest(
verdict=ConfidenceVerdict.CHAIN_INFLATED,
recomputed_confidence=recomputed,
directive=f"HALT: Reported {chain.reported_chain_confidence:.2f} > recomputed {recomputed:.2f}.",
manifest_digest=cls._hash(f"INFLATED:{chain.reported_chain_confidence:.2f}:{recomputed:.2f}")
)
# Gate 5: Sound
digest = cls._hash(json.dumps({
"chain": chain.chain_id,
"reported": f"{chain.reported_chain_confidence:.3f}",
"recomputed": f"{recomputed:.3f}",
"method": chain.aggregation_method
}, sort_keys=True))
return ConfidenceManifest(
verdict=ConfidenceVerdict.CHAIN_SOUND,
recomputed_confidence=recomputed,
directive=f"PROCEED: Chain confidence sound ({recomputed:.2f} via {chain.aggregation_method}).",
manifest_digest=digest
)