--- eip: 8498 title: Namespaced State Transitions for SBTs description: An interface for policy-validated, versioned state transitions across namespaces of a soulbound token. author: XiaoHai (@XiaoHai67890) discussions-to: https://ethereum-magicians.org/t/erc-8498-namespaced-state-transitions-for-sbts/30361 status: Draft type: Standards Track category: ERC created: 2026-11-13 requires: 165, 712, 721, 5192 --- ## Abstract This proposal defines an interface for evolving the state of a permanently non-transferable token. Each token contains independently governed namespaces, each with a versioned state commitment and an identified verification policy. A transition binds its evidence and authorization to an exact predecessor, policy epochs, and a proposed next state. The interface provides state reads, policy reads, and atomic transition submission, allowing applications to interpret accumulated experience under explicit rules while checking current validity separately. ## Motivation Membership, qualifications, contributions, and recognized experience can change throughout a credential's lifetime. A holder may participate in governance, transfer assets, trade, borrow, contribute to a community, or obtain an off-chain attestation. Different applications can use these histories for different purposes without requiring a new credential for every update. Existing token standards support non-transferability, mutable attributes, and several forms of issuance, revocation, and authorization. An application still needs a shared way to determine which part of a credential changed, which rules governed the change, whether the update extended the intended predecessor, and whether the accepted state remains usable. Accumulation and current validity are distinct. A historical experience may remain recorded after its qualification expires. A correction can preserve the original claim while replacing its current interpretation. Suspending a namespace need not erase its history or affect unrelated namespaces. These distinctions require explicit state and policy semantics. ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. ### Credential identity and interface discovery An implementation MUST implement [ERC-721](./eip-721.md), [ERC-165](./eip-165.md), and [ERC-5192](./eip-5192.md). For every existing token, `locked(tokenId)` MUST return `true`. Every account-to-account transfer, including a transfer to the same account, MUST revert. Minting and burning MAY be supported under implementation-defined authorization. A burned token identifier MUST NOT be reused. These requirements preserve the token's owner address throughout its live existence; they do not establish natural-person uniqueness or prevent changes to an owning account's control. A namespace is an opaque, nonzero `bytes32` identifier registered within a credential contract. State identity is the tuple: ```text (chainId, credentialContract, tokenId, namespace) ``` The same namespace value in another credential contract does not imply the same policy or meaning. Namespace registration, enumeration, and administrative interfaces are outside this specification. Implementations MUST support the following interface and MUST return `true` from `supportsInterface(0x86ebb4d4)`. This identifier covers the three methods declared below; inherited interfaces are advertised separately. ```solidity pragma solidity ^0.8.24; interface IEvolvingSBT { enum NamespaceStatus { Uninitialized, Active, Suspended, Revoked } struct StateHead { uint64 revision; bytes32 stateRoot; bytes32 policyId; NamespaceStatus status; uint64 validUntil; } struct PolicyDefinition { bytes32 schemaId; bytes32 rulesHash; address verifier; bytes32 verifierConfigHash; bytes32 authorizationHash; string specificationURI; } struct PolicyStatus { uint64 epoch; bool updatesEnabled; bool usesEnabled; } struct StateUpdate { bytes32 stateRoot; bytes32 policyId; NamespaceStatus status; uint64 validUntil; } struct Transition { uint256 tokenId; bytes32 namespace; uint64 expectedRevision; bytes32 expectedStateHash; uint64 sourcePolicyEpoch; uint64 targetPolicyEpoch; StateUpdate nextState; bytes32 operationsCommitment; uint64 deadline; } function stateOf(uint256 tokenId, bytes32 namespace) external view returns (StateHead memory); function policyOf(bytes32 namespace, bytes32 policyId) external view returns (PolicyDefinition memory definition, PolicyStatus memory status); function applyTransition( Transition calldata change, bytes calldata evidence, bytes calldata authorization ) external returns (uint64 newRevision); event StateTransitioned( uint256 indexed tokenId, bytes32 indexed namespace, uint64 revision, bytes32 previousStateHash, bytes32 nextStateHash, bytes32 policyId, bytes32 transitionId ); } ``` ### State reads `stateOf` MUST return the latest accepted head for the specified token and namespace. It MUST revert if the token does not exist, has been burned, or the namespace is unknown. For a live token and a registered namespace that has never been initialized, it MUST return: ```text revision = 0 stateRoot = bytes32(0) policyId = bytes32(0) status = Uninitialized validUntil = 0 ``` An initialized head MUST have a nonzero revision and a registered, nonzero `policyId`. It MUST NOT return to `Uninitialized` or reset its revision. `stateRoot` commits to the state defined by the policy; its contents are not standardized by this interface. `validUntil` is a Unix timestamp in seconds. Zero denotes no namespace-level expiration. A finite validity bound includes its timestamp: the head is expired when `block.timestamp > validUntil`. Time passing MUST NOT itself modify the stored head, its revision, or its status. An expired head can therefore retain `Active` status. Whether it accepts further updates is determined by the transition rules, independently of whether it is currently usable. ### Policies and profiles `policyOf` MUST return the immutable definition and current live controls for a policy registered in the specified namespace. It MUST revert for an unknown namespace or policy. Error selectors are implementation-defined. The definition fields have the following meanings: | Field | Meaning | | --- | --- | | `schemaId` | Identifies the state and evidence data model. | | `rulesHash` | Identifies the fixed transition and interpretation rules. | | `verifier` | Identifies the contract responsible for verification; it can be the credential contract itself. | | `verifierConfigHash` | Commits to verification configuration under a profile-defined encoding. | | `authorizationHash` | Commits to authorization rules and authority configuration under a profile-defined encoding. | | `specificationURI` | Locates the specification of the identified profile. | A profile is the concrete definition of a schema, transition rules, evidence encoding, and authorization mechanism used by a policy. Its specification MUST define: 1. The derivation and fixed meaning of the identifiers and configuration commitments above. 2. The state-root construction and the encodings of `evidence` and `authorization`. 3. Accepted evidence sources, their trust assumptions, and how evidence is attributed to the credential's subject. 4. The authorized parties or proof conditions for every supported operation, including initialization and administrative changes. 5. How verification establishes that the proposed next state follows from the predecessor and accepted operation. 6. Expiration, correction, revocation, migration, and duplicate-accounting behavior, where supported. 7. The upgrade and control assumptions of verifier and authority contracts. Identifiers MAY be derived from canonical schema or rule names rather than document bytes. `specificationURI` is a discovery location; its content MUST NOT redefine an existing policy. A verifier interface is not prescribed by this standard. Once registered, a `PolicyDefinition` MUST NOT change in place. Different definitions MUST use different policy identifiers. Policies referenced by accepted heads MUST remain queryable. Implementations MUST NOT silently reinterpret an accepted head under a different policy. The policy's live controls have separate effects: - `updatesEnabled` determines whether that policy permits new transitions. - `usesEnabled` determines whether consumers may rely on that policy for current credential use. The policy epoch MUST start at `1`. Changing either live control MUST increase the epoch by exactly one. A call that changes neither control MUST leave the epoch unchanged. An epoch MUST NOT reset or wrap. Epochs and controls are live policy data, not fields of the token's state head. Administration MAY expose additional methods and events, but every path that changes a head or policy controls MUST preserve the requirements in this specification. ### Canonical encodings All hashes below use Ethereum Keccak-256. Unless explicitly specified otherwise, tuples use Solidity `abi.encode`, with the field types shown in the interface. Enum values use their zero-based `uint8` representation. Strings use their UTF-8 bytes. For a head `h`, the canonical state hash MUST be: ```solidity keccak256(abi.encode(h)) ``` This is the encoding of the five `StateHead` fields in declaration order, without an additional type hash. The hash of the uninitialized head is the hash of 160 zero bytes, not `bytes32(0)`. For namespace `n` and definition `d`, the policy identifier MUST be: ```solidity keccak256(abi.encode( n, d.schemaId, d.rulesHash, d.verifier, d.verifierConfigHash, d.authorizationHash, keccak256(bytes(d.specificationURI)) )) ``` Policy references MUST retain their chain, credential-contract, and namespace context; `policyId` alone is not a globally unique policy identity. For every submitted transition: ```solidity change.operationsCommitment == keccak256(evidence) ``` MUST hold for the exact submitted bytes. A profile MUST NOT substitute a hash of a differently encoded or normalized operation. The canonical `transitionId` MUST be the [EIP-712](./eip-712.md) digest defined below. Using this digest as an identifier does not require signature-based authorization. The domain fields MUST be: ```text name: EvolvingSBT version: 1 chainId: current execution chain ID verifyingContract: credential contract address ``` The domain type is: ```text EIP712Domain(string name,string version,uint256 chainId,address verifyingContract) ``` The type hashes are the Keccak-256 hashes of these exact strings: ```text StateUpdate(bytes32 stateRoot,bytes32 policyId,uint8 status,uint64 validUntil) Transition(uint256 tokenId,bytes32 namespace,uint64 expectedRevision,bytes32 expectedStateHash,uint64 sourcePolicyEpoch,uint64 targetPolicyEpoch,StateUpdate nextState,bytes32 operationsCommitment,uint64 deadline)StateUpdate(bytes32 stateRoot,bytes32 policyId,uint8 status,uint64 validUntil) ``` Let their hashes be `STATE_UPDATE_TYPEHASH` and `TRANSITION_TYPEHASH`. The digest MUST be computed as follows: ```solidity bytes32 nextHash = keccak256(abi.encode( STATE_UPDATE_TYPEHASH, change.nextState.stateRoot, change.nextState.policyId, change.nextState.status, change.nextState.validUntil )); bytes32 structHash = keccak256(abi.encode( TRANSITION_TYPEHASH, change.tokenId, change.namespace, change.expectedRevision, change.expectedStateHash, change.sourcePolicyEpoch, change.targetPolicyEpoch, nextHash, change.operationsCommitment, change.deadline )); bytes32 transitionId = keccak256(abi.encodePacked( hex"1901", domainSeparator, structHash )); ``` Here `domainSeparator` is the EIP-712 domain separator for the exact domain above. `authorization` is excluded from the digest. It authenticates or proves acceptance of the transition rather than defining its identity. A profile MAY place operation descriptors in `evidence` and a transition-bound proof in `authorization`. A proof that binds `transitionId` cannot itself be included in the evidence committed by that identifier without creating a circular hashing dependency. ### Transition acceptance `applyTransition` MUST validate and commit atomically. It MUST reject a transition unless all of the following hold at acceptance: 1. The token exists and the namespace is registered. 2. `expectedRevision` equals the current head's revision, and `expectedStateHash` equals its canonical state hash. 3. `deadline` is nonzero and `block.timestamp <= deadline`. 4. `operationsCommitment` matches the exact submitted evidence. 5. The target policy is registered in the namespace, its epoch equals `targetPolicyEpoch`, and its `updatesEnabled` is `true`. 6. For an uninitialized head, `sourcePolicyEpoch` is zero. Otherwise, the source policy is the current head's policy, its epoch equals `sourcePolicyEpoch`, and its `updatesEnabled` is `true`. 7. The lifecycle transition is permitted by the table below. 8. Evidence and authorization satisfy the governing policy or policies, including subject attribution and the relationship between the predecessor and proposed next state. 9. The next revision can be represented by `uint64` without overflow. Verification MUST cover the entire transition, including its identity domain, predecessor, policy epochs, proposed state, evidence commitment, and deadline. Profiles MAY use signatures, an authorized on-chain caller, proofs, or a combination. Relaying a transaction MUST NOT itself confer recognition or administrative authority. Initialization MUST obtain holder consent to the initial transition in addition to the policy's required authorization. A direct call from the holder or an authorization bound to the same transition MAY establish this consent. Owning the SBT MUST NOT automatically authorize claims that the profile assigns to an issuer, controller, or other verifier. | Previous status | Permitted next statuses | | --- | --- | | `Uninitialized` | `Active` | | `Active` | `Active`, `Suspended`, `Revoked` | | `Suspended` | `Suspended`, `Active`, `Revoked` | | `Revoked` | None | Profiles MAY impose stricter restrictions. `Revoked` is terminal for the token and namespace. Ordinary experience-update authority MUST NOT implicitly grant authority to resume a suspended namespace, alter its validity, or migrate its policy; each such effect MUST be authorized under explicit profile rules. On success, the implementation MUST assign `newRevision = previousRevision + 1`, store the four fields from `nextState` together with that revision, and return the revision. It MUST emit exactly one `StateTransitioned` event for the accepted transition, containing the canonical previous and next state hashes, resulting policy identifier, and canonical transition identifier. A transition MUST NOT alter another token's or namespace's head. All accepted changes to a head, including administrative changes, MUST follow these rules and emit the event. If any validation fails, the call MUST revert without retaining head changes, profile bookkeeping changes, or events from the call. Implementations MUST prevent callbacks from invalidating the checked predecessor, applicable epochs, or token existence before commitment. Reentrancy protection or equivalent checks MAY be used. ### Optional policy migration Implementations MAY reject every transition that changes an initialized head's `policyId`. If supported, migration MUST be explicitly permitted by both source and target policies. It MUST satisfy both policies' migration authorization and state-conversion rules, bind their current epochs, and preserve revision continuity. It MUST NOT bypass suspension authority, terminal revocation, or any historical-preservation guarantee of the source profile. No separate migration method is required. ### Growth profiles The core records acceptance of a state transition. A sequence of increasing revisions alone does not establish append-only history, truth of external facts, or absence of duplicate experience. A profile claiming append-only growth MUST define and enforce a commitment relation that preserves previously accepted historical entries. Correction or revocation MUST be represented without silently removing those entries from the committed history. A profile that counts distinct experiences MUST define canonical experience identity and the scope of duplicate detection. Changing a claim identifier or authorization MUST NOT grant additional credit for the same experience within that scope. If corrections are supported, the profile MUST define how the replacement relates to the original experience and affects current validity and credit. Historical preservation does not require all entries to remain currently valid. Profiles MAY maintain separate commitments for history, current claim status, and credited experience. The core does not mandate a Merkle tree, accumulator, score, label taxonomy, or individual-claim revocation registry. ### Capacity exhaustion A bounded profile MUST specify the behavior of append, correction, and invalidation operations when its admission or history capacity is exhausted. Exhaustion of either capacity MUST NOT by itself disable every authorized path for preventing reliance on affected state. Such a path MAY invalidate an individual claim, suspend or revoke the affected namespace, or disable use of the governing policy. Its authorization requirements and the scope of affected credentials MUST be specified. Capacity-independent availability does not override policy controls or the terminal `Revoked` status. Invalidation alone does not constitute correction: it prevents use of affected state without necessarily supplying a replacement record. A profile claiming continued correction after saturation MUST define a history-preserving mechanism, such as incremental history growth, segment rollover, or an explicitly authorized migration. Within the same token and namespace, this mechanism MUST preserve revision continuity, experience identity, and duplicate-accounting state. Any migration remains subject to the migration requirements above. Starting another token or namespace MUST NOT be represented as continuous correction without an additional protocol that establishes those relationships and prevents duplicate credit across the old and new scopes. If a profile allows correction while a namespace is suspended, correction authority MUST NOT implicitly authorize resumption. Corrective work and restoring current use MUST obey the profile's separately specified permissions. ### Current use and application proofs When a consumer treats a namespace as currently usable, it MUST establish that the token exists, the head is initialized and `Active`, the namespace has not expired, and the head's policy currently has `usesEnabled == true`. These are necessary conditions, not a sufficient application qualification. Consumers MUST also enforce their application predicate and any profile-specific claim validity requirements. A consumer using an earlier snapshot MUST explicitly identify it and its freshness requirements rather than present it as the current state. `updatesEnabled == false` does not by itself invalidate use. Conversely, `usesEnabled == false` prevents current use even if the state head and revision have not changed. Consumers MUST NOT infer current policy controls from a previously observed `StateTransitioned` event alone. External facts, including balances and independent revocation registries, can change without a transition. Applications requiring such facts MUST define the accepted snapshot, freshness, and finality rules for them. Private presentations and one-time entitlement consumption are outside the core interface. A presentation extension MUST define its relying-party and purpose domain, freshness, and subject authorization. An application claiming one-time use MUST enforce consumption state, such as a scoped nullifier, at the application level. Copying proof bytes cannot be prevented by this state interface. ## Rationale ### Three methods and one event `stateOf` identifies the accepted state. `policyOf` provides its interpretation and current policy controls. `applyTransition` accepts a verified successor. `StateTransitioned` supports indexing and audit of accepted changes. Generic attribute setters would leave evidence acceptance and predecessor binding to incompatible conventions. A universal qualification method would instead require this proposal to standardize application predicates, proof systems, and freshness rules. The selected interface standardizes the shared state layer while allowing those choices to be specified by profiles and applications. Explicit predecessor hashes protect all head fields, including policy and validity, while revisions provide a simple ordering. Policy epochs invalidate pending transitions after control changes, including a pause followed by a resume. An accepted state is not rewritten when live controls change. The canonical EIP-712 identifier makes transition identity consistent across implementations and separates chains and credential contracts. It is independent of signature representation and does not prescribe a particular authorization scheme. ### Namespaces and experience interpretation A credential can have separate namespaces for community contributions, education, financial activity, or memberships, governed by different policies. Suspending one namespace does not suspend another. Namespaces need not correspond to protocol names. A profile can interpret transfers, swaps, lending positions, prediction-market activity, or attestations across several sources. Explorer labels and protocol adapters can assist classification, but labels do not themselves establish source authenticity, subject attribution, or entitlement. Subject attribution and recognition authority answer different questions: whose experience is represented, and who or what may establish its meaning. Keeping both explicit prevents account ownership from being mistaken for authority to issue qualifications. ### Relationship to existing standards | Standard | Existing scope | Relationship to this proposal | | --- | --- | --- | | ERC-5192 | Lock detection and restrictions on transferring locked ERC-721 tokens. | Used as the base; this proposal additionally requires permanent locking and defines namespaced evolution. | | [ERC-5484](./eip-5484.md) | Consensual issuance, fixed burn authorization, and restrictions on changes to issued metadata. | Does not define the namespaced transition model. Compliance with this proposal does not imply compliance with ERC-5484. | | [ERC-5727](./eip-5727.md) | Semi-fungible SBTs, issuance, revocation, value changes, verification, and extensions for governance, delegation, expiration, and recovery. | Already supports dynamic behavior. This proposal defines independently versioned namespaces within one credential and a common predecessor-bound transition interface. | | [ERC-7508](./eip-7508.md) | Dynamic token attributes and granular attribute access control. | Attribute management can support implementations; it does not by itself specify this evidence-validated transition protocol. | | [ERC-7208](./eip-7208.md) | Separation of data storage and management through data objects, indexes, and managers. | Can support an implementation's architecture; it does not prescribe these credential transition semantics. | The proposed increment is a shared interface and acceptance model for namespaced state evolution. It does not claim that earlier SBT standards cannot represent changing credentials or be extended with similar functionality. ### Scope of privacy and recovery User-held material can supply witnesses for a proof under a compatible profile. A root alone does not provide privacy or data availability. The state layer therefore allows proof-based validation without selecting a proof system or promising unlinkability. Permanent token locking preserves the owner address, while a smart account can separately manage its keys. A protocol that burns and reissues a successor credential needs additional rules for identity continuity, consent, revocation, and replay protection. Such a recovery protocol is outside this proposal. ## Backwards Compatibility This proposal adds an interface to ERC-721 and ERC-5192 implementations. Existing tooling can continue to identify the token and inspect its locked status; understanding namespace state requires the new interface. An ERC-5192 implementation that permits unlocking does not satisfy this proposal's permanent-lock requirement. Existing transferable tokens cannot claim conformance merely by exposing the three additional methods. Compatibility with ERC-5484 or ERC-5727 requires a separate assessment of their requirements. In particular, leaving a `tokenURI` string unchanged does not establish that the referenced metadata satisfies ERC-5484's immutability requirement. This proposal neither overrides those standards nor claims their interface identifiers. ## Test Cases The following cases exercise the core requirements. They assume a live token, a registered namespace, and a profile that otherwise accepts the operation unless stated otherwise. | Input or condition | Expected result | | --- | --- | | Call `stateOf` before namespace initialization. | Return the all-zero head. | | Query an unknown namespace, unknown policy, or absent token as applicable. | Revert. | | Initialize with the zero head's hash, correct epochs, and required consent. | Return revision `1`; emit the canonical previous and next hashes. | | Use `bytes32(0)` as the uninitialized predecessor hash. | Revert. | | Submit two transitions against the same predecessor. | After one succeeds, the other reverts. | | Change evidence bytes without changing their commitment. | Revert. | | Submit with a stale source or target epoch. | Revert. | | Disable and then re-enable updates. | An authorization using the earlier epoch remains stale. | | Submit at the deadline; submit after it; use a zero deadline. | The first is not rejected for time; the latter two revert. | | Disable uses without changing the head. | `stateOf` stays unchanged; current use fails the policy-control check. | | Disable updates while uses remain enabled. | Transitions fail; otherwise eligible existing state can still be used. | | Advance time past finite validity. | The head is unchanged; current namespace use fails. | | Attempt any transition from `Revoked`. | Revert. | | Attempt a revision or epoch overflow. | Revert without wrapping. | | Attempt transfer between nonzero addresses, including self-transfer. | Revert. | | Query a burned token or attempt to reuse its identifier. | Reject. | | A callback changes a checked predecessor, epoch, or token existence. | Reject the conflicting operation or revert the enclosing transition. | The selectors and their XOR are: ```text stateOf(uint256,bytes32) = 0x2899ea5b policyOf(bytes32,bytes32) = 0xc24781ae applyTransition(Transition,bytes,bytes) = 0x6c35df21 IEvolvingSBT interface identifier = 0x86ebb4d4 ``` The last method uses the tuple expansion in the Solidity interface, not the literal word `Transition`, when computing its selector. The canonical uninitialized state hash is: ```text keccak256(160 zero bytes) = 0xdfded4ed5ac76ba7379cfe7b3b0f53e768dca8d45a34854e649cfc3c18cbd9cd ``` Growth-profile tests additionally exercise the profile's declared preservation and accounting guarantees, such as retaining an original claim after correction and refusing repeated credit for its experience identity. Tests at capacity verify the documented append and correction behavior and at least one authorized invalidation path with its required policy controls satisfied. A profile claiming continued correction also exercises correction at the capacity boundary, preservation of earlier history and duplicate-accounting state, and revision continuity. If correction while suspended is supported, it does not implicitly resume the namespace. ## Reference Implementation The following pseudocode illustrates the acceptance path. Profile verification supplies the operation-specific checks and any bookkeeping changes; it does not replace the core checks. ```text applyTransition(change, evidence, authorization): enter transition guard previous = stateOf(change.tokenId, change.namespace) check predecessor, deadline, and evidence commitment resolve target policy and, if initialized, source policy check epochs, update controls, and lifecycle id = canonicalTransitionId(change) verify profile evidence, authorization, and proposed result verify holder consent if initializing verify both policies if migrating ensure checked state, policies, and token remain valid next.revision = checkedAdd(previous.revision, 1) copy change.nextState into next commit profile bookkeeping and next atomically emit StateTransitioned with canonical hashes and id leave transition guard return next.revision ``` The experimental signed-growth reference profile uses one immutable policy per namespace, distinct issuer and controller addresses, and at most 32 accepted records per token and namespace. Its public record format is: ```solidity struct Record { bytes32 claimId; bytes32 experienceId; address subject; bytes32 contentHash; uint64 validUntil; bytes32 replaces; } ``` The issuer authorizes append and correction. The controller authorizes initialization, suspension, resumption, revocation, and finite validity extension. Initialization also requires holder consent. The record subject is the token owner. EOA signatures and [ERC-1271](./eip-1271.md) contract signatures authorize the canonical transition digest. This profile retains every accepted record in insertion order. Corrections append a new claim linked to a nonsuperseded claim with the same experience identifier. They supersede the original claim without granting new experience credit. Deduplication is permanent within the token and namespace; a different identifier supplied by a dishonest issuer can still misrepresent the same external action. The profile commits separately to ordered records, their current superseded flags, and the experience identifiers credited by append operations. Its resulting root is: ```solidity keccak256(abi.encode( PROFILE_ID, recordsRoot, uint64(recordCount), statusRoot, creditedActionsRoot )) ``` These roots use ordered hash chains, not Merkle trees. Corrections consume record capacity. Lifecycle operations preserve the growth root while changing the head's status or validity. The profile permits no policy migration and implements no ZK presentation or independent individual-claim revocation registry. These implementation choices are not universal requirements of the core interface. At 32 accepted records, both append and correction fail. Namespace suspension and revocation remain available subject to update controls, authorization, and lifecycle rules, but do not supply a replacement record. Separately, the controller can disable policy use for all credentials governed by that policy. The profile therefore demonstrates bounded growth and invalidation, not continued correction after saturation. ## Security Considerations ### Evidence and authority trust An accepted transition establishes compliance with a policy, not unconditional truth of an external event. A signature-based policy relies on its recognized authorities. A chain-proof policy relies on its verifier, source-chain authentication, finality assumptions, and interpretation rules. Explorer labels are classification inputs rather than proof of those properties. Deduplication prevents repeated credit only within the profile's identity scheme and scope. It does not independently detect two identifiers that describe the same external action. Non-transferability also does not establish one-person-one-credential or stop key sharing and account sale. ### Policy changes and mutable dependencies Immutable policy fields do not make external contracts immutable. Proxy upgrades, authority key changes, or changing contract-signature behavior can alter effective trust without changing an address. The profile's documented control model and consumer policy selection determine whether those changes are acceptable. The live epoch tracks policy-control changes defined here; it is not a universal counter for external account changes. Disabling updates can also prevent corrective transitions until updates are restored. Disabling uses provides a separate mechanism to stop reliance on affected state while its update path is unavailable. ### Replay and concurrent updates The transition digest binds chain, credential contract, token, namespace, predecessor, epochs, next state, evidence, and deadline. Successful application advances the revision, so the same predecessor cannot be reused. No token-ID reuse prevents a destroyed identity from being silently reintroduced. Two chains or forks sharing an identifier and compatible state can still share replay assumptions. Applications relying on cross-chain history need explicit finality and domain handling beyond this local transition interface. External verification and contract-account callbacks can create reentrancy risks. Validation that was correct before a callback can become stale afterward. Atomicity includes profile bookkeeping as well as the head. ### Current validity and stale proofs A historical inclusion proof does not establish current qualification. Expiration, supersession, namespace revocation, policy-use controls, independent revocation, and application-specific live facts can each change the result. Accepting an old root or cached policy status without a defined freshness rule can preserve a qualification after it should cease. Revocation is terminal only for a particular token and namespace. Minting another credential or using another namespace is not prevented by the core; applications requiring uniqueness or a broader revocation scope need corresponding admission rules. ### Privacy and proof use Public state roots, events, calldata, namespace identifiers, and update timing can reveal information. Hashing low-entropy data does not create a hiding commitment. User-held witnesses or encrypted storage do not by themselves make public updates unlinkable. A valid proof can be copied. Relying-party binding, purpose binding, subject authorization, and application-level consumption checks determine whether the copy can be used. The transition identifier is not an entitlement nullifier. Applications that allow only one redemption need atomic consumption across their accepted entry points; fresh challenges alone do not stop repeated redemption of the same entitlement. ### Availability and resource limits A state commitment does not ensure availability of its witnesses or referenced specification. Loss of user-held material can prevent future proofs even while the head remains valid. A mutable URI can also become unavailable or serve misleading content. Profiles with unbounded verification or historical reconstruction can make updates impractical. Bounded histories, incremental accumulators, and proof systems have different capacity and cost tradeoffs. A bounded profile needs explicit correction-capacity behavior because reaching its limit can prevent subsequent corrections. Reserved correction slots delay exhaustion but do not eliminate it. Removing a record cap without changing an algorithm that scans all historical records can instead make later correction unaffordable or exceed transaction gas limits. Segment rollover likewise needs defined counter bounds, witness availability, and preservation of deduplication across segments. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).