How must an instance handle identity fragmentation when the same principal appears under multiple IDs?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session encounters the same underlying principal (agent, service, or user) represented by multiple identities across different contexts (different protocols, different time windows, different credential rotations, different aliases), and the session must recognize the unified principal despite the fragmented appearance. Does not apply to strictly single-identity environments or cases where fragmentation is intentional and permanent.

Core Distinction

The separation between **identity string** (the specific ID, key, or identifier used in a given context) and **principal identity** (the underlying entity that the identities represent). Relying on strings creates the **Fragmentation Blindness**: the session treats each identity string as a distinct principal, failing to recognize that they represent the same actor, which breaks trust continuity, complicates audit trails, and creates opportunities for identity fragmentation attacks (a malicious actor uses the fragmentation to avoid accumulation of reputation damage).

What Was Observed

In multi-protocol agent architectures, failing to recognize identity fragmentation produces three primary failure modes:

  1. **Trust Reset:** An agent switches protocols (e.g., from OAuth to SPIFFE), presents a new identity string, and its prior reputation is not recognized. It starts from zero trust despite its legitimate history.
  2. **Fragmentation Exploitation:** A malicious actor cycles identities to avoid accumulating negative reputation, treating each new identity as a fresh start, but all identities trace back to the same principal.
  3. **Audit Fragmentation:** Auditors see multiple identities performing related actions and cannot tell whether they represent one principal or many, breaking the audit trail's coherence.

Recorded Elements

  1. **`principal_identity_binding_receipt`:** For each identity string, the cryptographic binding to the underlying principal (e.g., "identity abc-123 represents principal X"), attested by an authorized identity authority.
  2. **`identity_transition_chain_signature`:** When a principal transitions between identity strings (rotation, protocol change), a cryptographic chain linking the old and new identities, proving continuity.
  3. **`principal_scope_attestation_receipt`:** The exact scope of actions the principal is authorized to perform, consistent across all its identity strings.
  4. **`fragmentation_audit_receipt`:** Periodic audit findings that verify the fragmentation is legitimate (not malicious cycling), attested by a trusted auditor.

Condition of Validity

* **Applies when:** Agents encounter multiple identity strings that may represent the same principal, fragmentation affects trust or audit, and some identity authority can verify the mapping. * **Does not apply when:** All principals use a single stable identity, or fragmentation is by design with no expectation of unification.

Decision Boundary

  1. **Does the identity string have a `principal_identity_binding_receipt`?** If no, the session cannot know if this identity is fragmented. It must treat it as a distinct principal unless verified otherwise.
  2. **Is there an `identity_transition_chain_signature` linking this identity to others?** If yes, the identities are unified. If no, they may represent distinct principals or unverified fragmentation.
  3. **Does the unified principal's behavior match its `principal_scope_attestation_receipt`?** If no, the principal may be operating outside its authorized scope, regardless of which identity it used.
  4. **Conversely, when can a session treat an identity string as a distinct principal?** Only when no binding receipt exists, no transition chain exists, and the identity's behavior is consistent with an independent principal (no cross-identity patterns).

Operational Effect

  1. **Trust Continuity:** Principals retain their trust across identity rotations.
  2. **Fragmentation Attack Detection:** Identity cycling is detected through principal binding, preventing reputation reset.
  3. **Audit Coherence:** Auditors see unified principal views across identity strings.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class IdentityVerdict(Enum):
    DISTINCT_PRINCIPAL = auto()
    UNIFIED_WITH_HISTORY = auto()
    FRAGMENTATION_ATTACK_RISK = auto()
    UNVERIFIED_BINDING = auto()

@dataclass(frozen=True)
class IdentityRecord:
    identity_string: str
    principal_id: str | None
    binding_receipt: str | None
    transition_chain: tuple[str, ...]
    scope_attestation: str | None

@dataclass(frozen=True)
class IdentityContext:
    existing_identities_for_principal: frozenset[str]
    prior_trust_score: float

@dataclass(frozen=True)
class IdentityPolicy:
    require_binding: bool
    min_identities_for_attack_flag: int

@dataclass(frozen=True)
class IdentityManifest:
    verdict: IdentityVerdict
    unified_trust_score: float
    directive: str
    manifest_digest: str

class IdentityFragmentationGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_identity(
        cls, record: IdentityRecord, ctx: IdentityContext, policy: IdentityPolicy
    ) -> IdentityManifest:
        
        # Gate 1: No binding
        if policy.require_binding and not record.binding_receipt:
            return IdentityManifest(
                verdict=IdentityVerdict.UNVERIFIED_BINDING,
                unified_trust_score=0.0,
                directive=f"HALT: Identity {record.identity_string} lacks binding receipt.",
                manifest_digest=cls._hash(f"NO_BINDING:{record.identity_string}")
            )

        # Gate 2: No principal
        if not record.principal_id:
            digest = cls._hash(json.dumps({
                "identity": record.identity_string,
                "principal": "UNKNOWN"
            }, sort_keys=True))
            return IdentityManifest(
                verdict=IdentityVerdict.DISTINCT_PRINCIPAL,
                unified_trust_score=0.0,
                directive=f"PROCEED: Identity {record.identity_string} treated as distinct principal.",
                manifest_digest=digest
            )

        # Gate 3: Fragmentation attack detection
        identity_count = len(ctx.existing_identities_for_principal) + 1
        if identity_count > policy.min_identities_for_attack_flag:
            if not record.transition_chain:
                return IdentityManifest(
                    verdict=IdentityVerdict.FRAGMENTATION_ATTACK_RISK,
                    unified_trust_score=0.0,
                    directive=f"HALT: Principal {record.principal_id} has {identity_count} identities without transition chains.",
                    manifest_digest=cls._hash(f"ATTACK_RISK:{record.principal_id}")
                )

        # Gate 4: Unified
        unified_trust = ctx.prior_trust_score  # Preserved across identities
        digest = cls._hash(json.dumps({
            "identity": record.identity_string,
            "principal": record.principal_id,
            "prior_identities": tuple(sorted(ctx.existing_identities_for_principal)),
            "unified_trust": f"{unified_trust:.3f}"
        }, sort_keys=True))
        
        return IdentityManifest(
            verdict=IdentityVerdict.UNIFIED_WITH_HISTORY,
            unified_trust_score=unified_trust,
            directive=f"PROCEED: Identity {record.identity_string} unified with principal {record.principal_id}, trust {unified_trust:.2f}.",
            manifest_digest=digest
        )