--- eip: 8489 title: Agent Information Flow Paradigm description: Versioned rules for how agents provide, observe and redistribute information in a channel, with scoped role bindings and four-valued checks author: Gary Yang (@garyyang-finchip) discussions-to: https://ethereum-magicians.org/t/erc-8489-agent-information-flow-paradigm/30300 status: Draft type: Standards Track category: ERC created: 2026-11-08 requires: 165, 6372 --- ## Abstract This ERC defines the Agent Information Flow Paradigm (AIFP): a model of how information flows between autonomous agents, humans, tools and contracts, and an on-chain binding of that model. A flow paradigm is a content-addressed, versioned description of an interaction context: which carriers and structural operators it uses, and how each rule dimension (discovery, observation, contribution, management, redistribution, history, time, disclosure timing, contributor visibility, admission basis, representation, object relations, ordering) is set. A channel adopts exactly one paradigm version at any timepoint. Participation is recorded as separately addressable role bindings: each binding carries role bits, a scope, a basis, a validity interval and the rule version it was granted under, and it can be suspended, revoked and re-admitted on its own. Role acquisition is checked by composable predicates whose verification outcome is one of four values: satisfied, not satisfied, undeterminable, verifier error. Rule changes carry an explicit effective timepoint on the registry's [ERC-6372](./eip-6372.md) clock; disclosure records are append-only facts. The ERC standardizes three registries (paradigms, channels, participation), a predicate verifier interface, the canonical descriptor and subject-reference encodings, and optional profiles for event anchoring and absence semantics. Message transport is out of scope and attached through declared bindings. ## Motivation Agents already exchange information through many systems: task protocols, tool protocols, chat rooms, federated social feeds, signed-event relays, encrypted groups, notification channels and on-chain events. Each of these fixes its own notion of who may read, who may write, what history a late joiner sees and whether content may leave the room. None of them lets two independent implementations answer the same five questions about one interaction context in the same way: may this actor observe, may it contribute, may it publish what it read into another context, which version of the rules decides, and which conditions cannot be verified. Human communication offers a useful starting taxonomy - broadcast, panel, open assembly, closed meeting, acquainted peer-to-peer, referred peer-to-peer, stranger peer-to-peer - but these are platform-shaped observations. They mix structure (who speaks to whom), boundary (who may enter which role), time (synchronous or asynchronous) and relationship (how the parties met). They also miss forms that matter to agents: many-to-one intake where submitters cannot see each other, shared objects that agents change and observe without ever addressing each other, selective dispatch to one of several candidates, and the distinction between forwarding an object unchanged and deriving a new one from it. Agents also change the premises. Identity is cheap at the margin, so the stranger relationship is first a Sybil problem and admission must be a verifiable condition rather than a social fact. Speech can be signed, so provenance and the forward/derive distinction become checkable. What an agent reads may be executed, so a received message is an attack surface and the authority to act can never come from content. Memory is cheap, so retention is a policy to declare rather than a default. And a single venue can host agents as contributors and humans as observers at the same time, so boundaries are per role, not per venue. AIFP therefore separates the problems that existing systems fold together, so that each can be answered by itself: a generative model (two carriers, eight operators, thirteen rule dimensions and two extension dimensions) in which the familiar named forms are merely common combinations; three on-chain registries that give independent operators a shared, verifiable record of which rules a channel adopted, when they took effect, who holds which role in which scope on what basis, and what was disclosed to whom; and a four-valued verification result so that an unverifiable condition is never recorded as a failed one. Transport stays where it is; AIFP adds the rule and authority layer that transports can attach to. Readers who approach this specification from agent communication protocols may find the following reading useful: those protocols carry the messages, and AIFP states, for each context, who may observe, contribute and redistribute under which version of the rules, in a form that every participant and every transport can verify; in that reading a paradigm is a communication pattern, a channel is a communication context, a role binding is a listener, speaker, forwarder or moderator permission, and a transport binding is the attachment of a concrete protocol to the context. This is a reading aid for the definitions that follow, not an additional normative layer. ## 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](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174) when, and only when, they appear in all capitals, as shown here. ### 1. Terminology - **Actor**: a party that is identified, authorized or held responsible: an agent, a human, a tool or a contract. Actors are referenced by a subject reference (Section 3). - **Subject reference**: a `bytes32` derived from a canonical identifier string (Section 3). - **Information object**: a message, document, state version or other content, referenced on-chain by a `bytes32` (a content hash or a committed identifier). Content is never stored by the registries. - **Channel**: an interaction context (a topic, thread, archive, shared object or bounded activity) recorded in the channel registry. A channel adopts one paradigm version at any timepoint. - **Paradigm**: a flow paradigm descriptor (Section 5). It is identified by the keccak256 hash of its canonical encoding and never changes; a change is a new version in the same family. - **Family**: a named lineage of paradigm versions sharing one controller. - **Role binding**: a record that a subject holds a set of role bits in a scope of a channel, with a basis, a validity interval, a state and the rule version under which it was granted (Section 9). Each role binding has a `bindingId`. - **Transport binding**, **governance binding**, **economic binding**: declarations attached to a channel describing an external system through which the channel's messages travel, through which its rules or participation may be changed, or which carries its economic terms (Section 12). The word "binding" is always qualified in this document. - **Scope**: a `bytes32` naming a sub-context or object inside a channel; `bytes32(0)` denotes the whole channel. - **Timepoint**: a `uint48` read on the channel registry's ERC-6372 clock (Section 4). - **Outcome**: one of `SATISFIED` (0), `NOT_SATISFIED` (1), `UNDETERMINABLE` (2), `VERIFIER_ERROR` (3). ### 2. Vocabulary #### 2.1 Carriers Information travels on one or both of two carriers: - `delivery`: an object is delivered to addressed receivers. The addressed set, the set permitted to observe and the set that actually received are three different sets. - `shared-object`: participants interact by contributing to a common object and observing its changes, without necessarily addressing each other. #### 2.2 Structural operators | Operator | Meaning | | --- | --- | | `fan-out` | one source to many receivers | | `fan-in` | many sources to one or few receivers; sources need not observe each other | | `feedback` | bidirectional exchange with request-response correlation | | `n-party` | many participants contribute and observe each other | | `select` | a receiver is chosen from a candidate set by a rule | | `forward` | the same object enters another channel with its reference and integrity evidence unchanged | | `derive` | a new object is produced from one object and attributed to a new contributor | | `merge` | a new object is produced from several objects | A paradigm declares the set of operators it uses. Composition of operators is unrestricted; every combination is a candidate configuration, and the compatibility rules of Section 5.4 decide which candidates are valid. #### 2.3 Rule dimensions Each dimension answers one question and is declared separately. `unspecified` means "not declared", never a default. | Dimension | Question | Separately declared values | | --- | --- | --- | | discovery | who can learn that the channel exists | `public-directory`, `directed`, `undiscoverable` | | observation | who can read what | read scope; submission visibility (whether contributors see each other's submissions); payload protection; metadata protection | | contribution | who can publish, submit, respond, modify | allowed actions; acquisition (`open`, `manager`, `predicate`, `closed`); transferability; expiry | | management | who controls what | controllers of admission, ordering, removal, rules and disclosure, each a set drawn from `source`, `moderator`, `protocol`, `decision-body`, `none` | | redistribution | whether content may leave the channel | forward, quote, derive, publish elsewhere, each allowed or not | | history | what a late joiner sees and for how long | late-joiner visibility; retention; mutability of objects | | time | how interaction is organized in time | realtime requirement; request-response correlation; delivery (`push`, `pull`, `stream`, `batch`); batching; clock (always the registry clock); absence semantics (`none` or `rule-defined`) | | disclosure timing | when an object becomes observable to an audience | `immediate`, `scheduled`, `conditional`, `commit-reveal` | | contributor visibility | who knows who contributed | `identified`, `pseudonymous`, `anonymous-qualified`, `anonymous`, `per-audience` | | admission basis | how the parties met and on what the admission rests | `prior-link`, `referral`, `open-discovery`; identity, qualification, trust and authorization are referenced separately | | representation | whether an actor may act for a principal | `allowed`, `disallowed` | | object relations | how an object relates to earlier ones | `replyTo`, `forwards`, `derivesFrom`, `synthesizes`, `references`, `supersedes`, `retracts` | | ordering | who sequences contributions | sequencer; total or partial order; whether the order is observable | Two extension dimensions are declared under `extensions`: `economic` (profile, chain, contract, asset, subject, amount or threshold, period, verifier) and `modality` (media, meanings, handling hints). An economic declaration is a declaration of terms, not a settlement; the registries hold no funds. #### 2.4 Roles A role binding carries a bitmask of role bits. Each bit is granted, suspended and revoked separately; there is no "member" flag. | Bit | Role | Meaning | | --- | --- | --- | | 1 | `DISCOVER` | may learn that the channel exists | | 2 | `OBSERVE` | may read objects within the scope | | 4 | `SUBMIT` | may publish or submit new objects | | 8 | `RESPOND` | may respond to existing objects | | 16 | `MODIFY` | may modify shared objects | | 32 | `MANAGE_ADMISSION` | may grant, suspend and revoke role bindings in its scope | | 64 | `MANAGE_ORDER` | may sequence contributions | | 128 | `MANAGE_REMOVE` | may remove objects from provision | | 256 | `MANAGE_RULES` | may adopt paradigm versions and declare bindings | | 512 | `REDISTRIBUTE` | may forward, quote or derive outside the channel | | 1024 | `MANAGE_DISCLOSURE` | may record disclosures and restrictions | Bits above 1024 are reserved. A function that takes a single role MUST reject a value with zero or more than one bit set, or with a reserved bit set. #### 2.5 Outcomes and composition Every verification answers one of four outcomes. `UNDETERMINABLE` and `VERIFIER_ERROR` MUST NOT be recorded or reported as `NOT_SATISFIED`. | Code | Outcome | Meaning | | --- | --- | --- | | 0 | `SATISFIED` | the condition holds under the evaluated rule version | | 1 | `NOT_SATISFIED` | the condition is shown not to hold | | 2 | `UNDETERMINABLE` | a necessary condition cannot be verified (unknown required extension, missing proof, data not available) | | 3 | `VERIFIER_ERROR` | a verifier has no code, reverted, ran out of the gas it was given, or returned malformed or out-of-range data | Composition is fixed. For `allOf`: if any child is `NOT_SATISFIED` the result is `NOT_SATISFIED`; otherwise if any child is `UNDETERMINABLE` the result is `UNDETERMINABLE`; otherwise if any child is `VERIFIER_ERROR` the result is `VERIFIER_ERROR`; otherwise `SATISFIED`. For `anyOf`: if any child is `SATISFIED` the result is `SATISFIED`; otherwise if any child is `UNDETERMINABLE` the result is `UNDETERMINABLE`; otherwise if any child is `VERIFIER_ERROR` the result is `VERIFIER_ERROR`; otherwise `NOT_SATISFIED`. A value above 3 is normalized to `VERIFIER_ERROR` before composition. A fault in one leaf is that leaf's `VERIFIER_ERROR` and nothing more: it MUST NOT abort the evaluation of the other leaves or of the tree. Child lists MUST be non-empty. There is no negation operator in this version. An enforcement point MAY refuse to proceed on any outcome other than `SATISFIED`. A record of such a refusal MUST keep the outcome that was obtained. ### 3. Subject references A subject reference is `keccak256` of the UTF-8 bytes of a canonical string of the form `::`. Scheme and domain are lowercase ASCII and never contain `:`; the identifier is everything after the second `:` and MAY contain `:`. | Scheme | Domain | Identifier | Example canonical string | | --- | --- | --- | --- | | `eip155-address` | chain id, decimal, no leading zeros | `0x` and 40 lowercase hex digits | `eip155-address:11155111:0x0000000000000000000000000000000000000001` | | `erc8004` | `:` with the registry address as `0x` and 40 lowercase hex digits | agent id, decimal | `erc8004:11155111:0x8004a818bfb912233c491871b3d84c89a494bd9e:10387` | | `did` | DID method, lowercase | method-specific identifier as given | `did:aid:example-7` | | `ext` | namespace, lowercase, characters `a-z0-9.-` | opaque, non-empty | `ext:finchip.ai:foga-cell-7` | The `erc8004` scheme refers to an agent registered in an [ERC-8004](./eip-8004.md) identity registry. There is exactly one encoding: an address MUST NOT be left-padded into a `bytes32` and used as a subject reference. Consumers verify a reference by recomputing it from the canonical string supplied in a descriptor or proof. A plain reference is not a hiding commitment, because identifiers are guessable; committed (salted) subjects are a separate profile with their own proof rules (Section 13.3). The reference of the account that sends a transaction on the current chain is `keccak256("eip155-address:" || decimal(block.chainid) || ":" || lowercaseHex(msg.sender))`. Registry functions that let a subject act for itself resolve `msg.sender` this way. ### 4. Clock Each registry deployment MUST implement ERC-6372 (`clock()` and `CLOCK_MODE()`). All timepoints in a deployment (adoption effective timepoints, validity intervals, suspension intervals, disclosure timepoints, evaluation timepoints) are read on that clock. The participation registry of a deployment MUST report the same clock and clock mode as its channel registry: one deployment is one clock domain, and the clock domain is immutable for the life of the deployment. The reference implementation uses `mode=timestamp`. Ordering within one timepoint is fixed by registration order: a later-recorded adoption with the same effective timepoint supersedes an earlier one, and state changes to role bindings apply in transaction order. A revocation at timepoint `t` excludes `t` from the binding's valid interval; a grant whose validity starts at `t` includes `t`. ### 5. Paradigm descriptors and identifiers #### 5.1 Document and canonical form A paradigm descriptor is a JSON document conforming to the [descriptor schema](../assets/eip-8489/schemas/paradigm-descriptor.schema.json), whose predicates conform to the [predicate schema](../assets/eip-8489/schemas/predicate.schema.json). Its canonical form is the JSON Canonicalization Scheme of [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) applied to the document (object members sorted by UTF-16 code units, no insignificant whitespace, integers only). The paradigm identifier is: ```text paradigmId = keccak256(utf8(JCS(descriptor))) familyId = keccak256(utf8(descriptor.family)) labelId = keccak256(utf8(label)) ``` The descriptor declares: `aifp` (the constant `"1"`), `family`, `version` (informative), optional `title`, `labels`, `carriers`, `operators`, `cardinality` (`contributors` in `single`, `roster`, `qualified`, `open`; `pairwise`), the thirteen `dimensions` of Section 2.3, optional `predicates` keyed by role name, optional `requiredBindings.capabilities` (Section 12) and optional `extensions` (`economic`, `modality`, `absence`, `ext`). A descriptor whose `dimensions.time.absence` is `rule-defined` MUST carry `extensions.absence` (rule, window length, objection role, default outcome); a descriptor whose `absence` is `none` MUST NOT. #### 5.2 Labels and identification conditions Labels name common configurations. A label in the reserved namespace `aifp:paradigm//v1` MUST be one of the eleven names below, and a descriptor carrying it MUST satisfy the identification condition, which is decidable from the descriptor alone. Conditions may overlap, so a descriptor may carry several labels. Labels in `aifp:ext///vN` are not checked by this specification. | Name | Identification condition | | --- | --- | | `source` | `cardinality.contributors` is `single` and `operators` contains `fan-out` | | `intake` | `operators` contains `fan-in` and `observation.submissionVisibility` is `collector-only` | | `direct` | `cardinality.pairwise` is true and `operators` contains `feedback` | | `panel` | `cardinality.contributors` is `roster`, `operators` contains `n-party`, `observation.readScope` is `open` or `predicate`, and `contribution.acquisition` is `manager` or `closed` | | `club` | `cardinality.contributors` is `qualified` or `open`, `operators` contains `n-party`, and `contribution.acquisition` is `open` or `predicate` | | `meeting` | `cardinality.contributors` is `roster`, `operators` contains `n-party`, `observation.readScope` is `members`, and `contribution.acquisition` is `manager` or `closed` | | `archive` | `carriers` contains `shared-object`, `time.delivery` contains `pull`, and `history.mutable` is false | | `blackboard` | `carriers` contains `shared-object`, `history.mutable` is true, and `operators` contains `n-party` | | `dispatch` | `operators` contains `select` | | `forward` | `operators` contains `forward` and `redistribution.forward` is true | | `derive` | `operators` contains `derive` or `merge`, and `redistribution.derive` is true | A label describes the identification condition and nothing else. Changing a value the condition does not mention (for example delivery from push to pull under `source`) does not change the label, and no label changes implicitly: a different label set is a different descriptor and therefore a new paradigm version. #### 5.3 Validation and division of responsibility The canonical validator (a reference is listed in the [assets index](../assets/eip-8489/README.md)) checks the descriptor against the schemas, checks the identification conditions of every reserved label, derives the per-role predicate encodings and hashes of Section 6, and produces the registration bundle. The paradigm registry checks only identity (the hash of the canonical bytes it receives), family control, succession and state; it does not interpret the descriptor, and `hasLabel` reports that a label was registered, not that the behaviour of any channel conforms to it. #### 5.4 Compatibility rules A validator MUST reject a descriptor that declares `commit-reveal` disclosure timing without a `delivery` carrier or a `shared-object` carrier that retains history; `anonymous-qualified` contributor visibility without a `credential` predicate for at least one contribution role; the `blackboard` label with `history.mutable` false; or an `ordering.sequencer` of `moderator` while `management.ordering` does not contain `moderator`. A descriptor that fails a compatibility rule is rejected at validation, never silently accepted. #### 5.5 Extensions `extensions.ext` lists third-party extensions by identifier `aifp:ext///vN` with a `required` flag. A consumer that does not understand a required extension MUST evaluate every predicate of the paradigm as `UNDETERMINABLE` and MUST NOT treat the paradigm as understood. Optional extensions are ignored when not understood. ### 6. Predicates #### 6.1 Encoding A predicate is a tree encoded as `abi.encode(Node[])`: ```solidity struct Node { uint8 kind; // see 6.2 uint8 childStart; // composites: index of the first child uint8 childCount; // composites: number of children, at least 1 address verifier; // ALLOWLIST: the subject set; delegated kinds: the verifier bytes32 ref; // leaf-specific reference bytes params; // leaf-specific parameters, opaque to the composite evaluator } ``` Node 0 is the root. The children of a composite node `i` are `nodes[childStart .. childStart + childCount)`, and every child index is greater than `i`. A tree MUST have between 1 and 64 nodes and a depth of at most 8; leaves MUST have `childCount == 0` and `childStart == 0`; composites MUST have `childCount >= 1`. The predicate hash committed by a paradigm is `keccak256(abi.encode(nodes))`. The canonical validator derives the node array from the JSON predicate breadth-first: the root takes index 0; when a composite is processed, its children are allocated as one contiguous block at the current end of the array, in order, and are then processed in the same order. This makes the encoding of a JSON predicate deterministic. #### 6.2 Kinds | Kind | Name | Verifier field | Ref | Params | | --- | --- | --- | --- | --- | | 0 | `open` | unused | 0 | empty; always `SATISFIED` | | 1 | `allowlist` | subject set contract | 0 | empty; `SATISFIED` if `contains(subject)` | | 2 | `external` | verifier | as declared | as declared | | 3 | `allOf` | unused | 0 | empty | | 4 | `anyOf` | unused | 0 | empty | | 5 | `credential` | verifier | scheme identifier | `abi.encode(uint256 minLevel, uint8 proofMode, uint256 freshnessSeconds, address[] issuers)` with `proofMode` 0 attested, 1 proved | | 6 | `stake` | verifier | 0 | `abi.encode(uint256 chainId, address contract, address asset, uint256 minAmount, uint8 period)` with `asset` zero for the native asset and `period` 0 active, 1 epoch, 2 lockup | | 7 | `identity` | verifier | registry address left-padded | `abi.encode(uint8 requiredState)` with 0 registered, 1 active, 2 bound | | 8 | `member` | verifier | group identifier | empty | | 9 | `decision` | verifier | decision identifier | empty | Kinds 5 to 9 are semantic leaves: they are delegated to the verifier exactly like kind 2, and the kind tells the verifier which vocabulary the parameters follow. A credential leaf typically refers to a know-your-agent attestation or zero-knowledge proof scheme; an identity leaf to an agent identity registry such as ERC-8004; a member leaf to a swarm or group registry; a decision leaf to a collective decision registry such as [ERC-8436](./eip-8436.md). This specification does not define those registries. #### 6.3 Verifier interfaces ```solidity interface IAIFPPredicateVerifier { function verify( bytes32 channelId, bytes32 subject, uint16 role, bytes32 scopeRef, uint8 kind, bytes32 ref, bytes calldata params, bytes calldata evidence ) external view returns (uint8 outcome); } interface IAIFPCompositeVerifier { function evaluate( bytes32 channelId, bytes32 subject, uint16 role, bytes32 scopeRef, bytes calldata predicate, bytes[] calldata evidence ) external view returns (uint8 outcome); function supportsKind(uint8 kind) external view returns (bool); } interface IAIFPSubjectSet { function contains(bytes32 subject) external view returns (bool); } ``` A leaf verifier MUST return an outcome and MUST NOT revert to signal a negative answer. `evidence[i]` is the caller-supplied evidence for node `i`; missing entries are empty. A composite verifier MUST judge a leaf call by its raw return data, not by whether that data decodes as the declared type: the call to `verify` MUST yield `VERIFIER_ERROR` for that leaf unless it succeeds and returns exactly one 32-byte word whose value is at most 3, and the call to `contains` MUST yield `VERIFIER_ERROR` for that leaf unless it succeeds and returns exactly one 32-byte word equal to 0 or 1. A composite verifier SHOULD bound the gas forwarded to each leaf call so that one leaf cannot exhaust the evaluation of the tree, MUST treat a leaf that exceeds that bound as `VERIFIER_ERROR` for that leaf, and MUST expose or document the bound. The reference implementation forwards at most 1,000,000 gas per leaf call (`LEAF_GAS_LIMIT`). #### 6.4 Evaluation order A composite verifier MUST evaluate in this order: (1) decode the tree and revert if it is malformed - a malformed predicate is a caller error, not an outcome; (2) check that every node kind is supported, and return `UNDETERMINABLE` for the whole predicate if any kind is not, before any composition; (3) evaluate leaves - `open` and `allowlist` natively, delegated kinds through the declared verifier, where a zero verifier address yields `UNDETERMINABLE`, and a verifier address with no code, a call that reverts or runs out of its gas bound, return data that is not exactly one 32-byte word, or a word outside the range of Section 6.3 yields `VERIFIER_ERROR` for that leaf alone, the remaining leaves still being evaluated; (4) combine children with the composition rules of Section 2.5. ### 7. Paradigm registry ```solidity interface IAIFPParadigmRegistry { struct RolePredicate { uint16 role; bytes32 predicateHash; } struct ParadigmRecord { bytes32 family; bytes32 supersedes; address registrant; uint48 registeredAt; string descriptorURI; } event ParadigmRegistered(bytes32 indexed paradigmId, bytes32 indexed family, bytes32 indexed supersedes, address registrant, string descriptorURI); event ParadigmDescriptor(bytes32 indexed paradigmId, bytes canonicalDescriptor); event FamilyControllerChanged(bytes32 indexed family, address indexed previousController, address indexed newController); function registerParadigm(bytes calldata canonicalDescriptor, bytes32 family, bytes32 supersedes, bytes32[] calldata labels, RolePredicate[] calldata rolePredicates, string calldata descriptorURI) external returns (bytes32 paradigmId); function transferFamily(bytes32 family, address newController) external; function paradigm(bytes32 paradigmId) external view returns (ParadigmRecord memory); function exists(bytes32 paradigmId) external view returns (bool); function hasLabel(bytes32 paradigmId, bytes32 label) external view returns (bool); function labelsOf(bytes32 paradigmId) external view returns (bytes32[] memory); function rolePredicateHash(bytes32 paradigmId, uint16 role) external view returns (bytes32); function familyController(bytes32 family) external view returns (address); } ``` Rules: - `paradigmId` MUST equal `keccak256(canonicalDescriptor)`; an empty descriptor and a duplicate identifier MUST be rejected. - The first registration in a family makes the caller the family controller; later registrations in the family MUST come from the controller. The controller MAY transfer the family. - `supersedes` MUST be zero or an existing paradigm of the same family. - Each `rolePredicates[i].role` MUST be a single defined role bit. A role with no predicate hash cannot be acquired through the admission path of Section 9.3. - The registry MUST emit the canonical descriptor bytes once, in `ParadigmDescriptor`, so that the paradigm can be reconstructed from the event log. - The registry MUST NOT interpret the descriptor (Section 5.3). ### 8. Channel registry ```solidity interface IAIFPChannelRegistry { enum ChannelState { Active, Frozen, Closed } struct Adoption { bytes32 paradigmId; uint48 effectiveAt; uint48 recordedAt; bool requiresReadmission; bytes32 authorizationRef; } struct BindingDecl { uint8 kind; bytes32 target; bytes32 version; uint32 capabilities; uint32 effectScope; uint8 adoptionMode; bytes32 authorizationRef; bool active; } struct ChannelRecord { address authority; bytes32 parent; ChannelState state; uint48 openedAt; string metadataURI; } event ChannelOpened(bytes32 indexed channelId, bytes32 indexed paradigmId, address indexed authority, bytes32 parent, string metadataURI); event ParadigmAdopted(bytes32 indexed channelId, bytes32 indexed paradigmId, uint48 effectiveAt, bool requiresReadmission, bytes32 authorizationRef, uint256 adoptionIndex); event BindingDeclared(bytes32 indexed channelId, bytes32 indexed bindingId, uint8 kind, bytes32 target, bytes32 version, uint32 capabilities, uint32 effectScope, uint8 adoptionMode, bytes32 authorizationRef); event BindingRevoked(bytes32 indexed channelId, bytes32 indexed bindingId); event DisclosureRecorded(bytes32 indexed channelId, bytes32 indexed objectRef, uint8 kind, bytes32 audienceRef, bytes32 authorizationRef, uint48 at, uint256 recordIndex); event ChannelStateChanged(bytes32 indexed channelId, ChannelState state); event AuthorityTransferred(bytes32 indexed channelId, address indexed previousAuthority, address indexed newAuthority); function openChannel(bytes32 paradigmId, address authority, bytes32 parent, string calldata metadataURI, BindingDecl[] calldata bindings) external returns (bytes32 channelId); function adoptParadigm(bytes32 channelId, bytes32 paradigmId, uint48 effectiveAt, bool requiresReadmission, bytes32 authorizationRef) external; function declareBinding(bytes32 channelId, BindingDecl calldata decl) external returns (bytes32 bindingId); function revokeBinding(bytes32 channelId, bytes32 bindingId) external; function recordDisclosure(bytes32 channelId, uint8 kind, bytes32 objectRef, bytes32 audienceRef, bytes32 authorizationRef) external; function setState(bytes32 channelId, ChannelState state) external; function transferAuthority(bytes32 channelId, address newAuthority) external; function channel(bytes32 channelId) external view returns (ChannelRecord memory); function currentParadigm(bytes32 channelId) external view returns (bytes32 paradigmId, uint48 effectiveSince); function paradigmAt(bytes32 channelId, uint48 timepoint) external view returns (bytes32 paradigmId); function readmissionEpochAt(bytes32 channelId, uint48 timepoint) external view returns (uint32); function adoptionCount(bytes32 channelId) external view returns (uint256); function adoptionAt(bytes32 channelId, uint256 index) external view returns (Adoption memory); function binding(bytes32 channelId, bytes32 bindingId) external view returns (BindingDecl memory); function bindingIds(bytes32 channelId) external view returns (bytes32[] memory); function isGovernanceExecutor(bytes32 channelId, address executor, uint32 effect) external view returns (bool); function disclosureCount(bytes32 channelId) external view returns (uint256); function disclosureAt(bytes32 channelId, uint256 index) external view returns (uint8 kind, bytes32 objectRef, bytes32 audienceRef, bytes32 authorizationRef, uint48 at); } ``` #### 8.1 Adoption - Opening a channel records its first adoption at the current clock value with `requiresReadmission` false. `paradigmId` MUST exist in the paradigm registry; `parent`, when non-zero, MUST be an existing channel. - `adoptParadigm` appends an adoption. `effectiveAt` MUST NOT be earlier than the current clock value. Adoptions are never modified or removed. - `paradigmAt(channelId, t)` returns the paradigm of the last adoption whose `effectiveAt <= t`, ties resolved in favour of the later-recorded adoption, and zero before the first adoption. `currentParadigm` is `paradigmAt` at the current clock value. - `readmissionEpochAt(channelId, t)` is the number of adoptions with `requiresReadmission` true and `effectiveAt <= t`. Role bindings admitted before a re-admission epoch are pending from that epoch onward (Section 9.4). - `authorizationRef` references the evidence that authorized the adoption (for example a collective decision). It confers no power by itself; the caller's power comes from Section 8.4. #### 8.2 Transport, governance and economic bindings `BindingDecl.kind` is 0 transport, 1 governance of rules, 2 governance of disputes, 3 governance of safety, 4 economic. `target` is the endpoint identifier of a transport or the left-padded address of a governance or economic contract; `version` identifies the protocol or policy version. `capabilities` is the transport capability bitmask of Section 12; a transport binding MUST have `effectScope == 0`. `effectScope` is a bitmask of governance effects: 1 rules (may adopt paradigm versions), 2 participation (may grant, suspend and revoke role bindings), 4 disclosure (may record disclosures). `adoptionMode` is 0 automatic (the target executes directly) or 1 confirmed (the channel authority confirms each result). `isGovernanceExecutor(channelId, executor, effect)` is true only for the target of an active, non-transport, non-economic binding in automatic mode whose `effectScope` contains `effect`. #### 8.3 Disclosure records and state `recordDisclosure` appends a record of kind 0 (`DISCLOSED`: the audience was authorized to observe the object or object set) or kind 1 (`RESTRICTED`: future provision of the object to the audience was withdrawn). Records are append-only facts and are permitted in every channel state, including `Closed` (Section 11). `setState` moves a channel between `Active` and `Frozen`, or to `Closed`; `Closed` is terminal. In `Frozen` and `Closed` no adoption, binding declaration, grant or admission is accepted; evaluation of past and present timepoints, suspension and revocation of role bindings, and disclosure records continue to work. Closing a channel revokes nothing and undoes no disclosure. #### 8.4 Call permissions | Function | Permitted caller | | --- | --- | | `openChannel` | anyone; the caller names the authority | | `adoptParadigm`, `declareBinding`, `revokeBinding` | the channel authority; a subject holding `MANAGE_RULES` for the whole channel at the current clock value; an executor with the rules effect | | `recordDisclosure` | the channel authority; a subject holding `MANAGE_DISCLOSURE` for the whole channel; an executor with the disclosure effect | | `setState`, `transferAuthority` | the channel authority | ### 9. Participation registry ```solidity interface IAIFPParticipationRegistry { enum BindingState { Granted, Suspended, Revoked } struct RoleBinding { bytes32 channelId; bytes32 subject; bytes32 onBehalfOf; uint16 roles; bytes32 scopeRef; uint8 basisKind; bytes32 basisRef; uint8 outcomeAtGrant; bytes32 ruleVersion; uint48 validFrom; uint48 validUntil; uint48 revokedAt; uint48 suspendedAt; uint48 reinstatedAt; address suspendedBy; uint32 admittedEpoch; BindingState state; } event RoleGranted(bytes32 indexed channelId, bytes32 indexed subject, bytes32 indexed bindingId, uint16 roles, bytes32 scopeRef, uint8 basisKind, bytes32 basisRef, uint8 outcomeAtGrant, bytes32 ruleVersion, uint48 validFrom, uint48 validUntil, bytes32 onBehalfOf); event RoleSuspended(bytes32 indexed channelId, bytes32 indexed bindingId, address indexed suspendedBy, bytes32 reasonRef, uint48 at); event RoleReinstated(bytes32 indexed channelId, bytes32 indexed bindingId, address indexed by, uint48 at); event RoleRevoked(bytes32 indexed channelId, bytes32 indexed bindingId, address indexed by, bytes32 reasonRef, uint48 at); event RoleReadmitted(bytes32 indexed channelId, bytes32 indexed bindingId, uint32 epoch, bytes32 ruleVersion, uint8 outcome); event AdmissionEvaluated(bytes32 indexed channelId, bytes32 indexed subject, uint16 role, bytes32 scopeRef, uint8 outcome); function grant(bytes32 channelId, bytes32 subject, bytes32 onBehalfOf, uint16 roles, bytes32 scopeRef, uint8 basisKind, bytes32 basisRef, uint48 validFrom, uint48 validUntil) external returns (bytes32 bindingId); function admit(bytes32 channelId, uint16 role, bytes32 scopeRef, bytes calldata predicate, bytes[] calldata evidence, uint48 validUntil) external returns (bytes32 bindingId); function previewAdmission(bytes32 channelId, bytes32 subject, uint16 role, bytes32 scopeRef, bytes calldata predicate, bytes[] calldata evidence) external view returns (uint8 outcome); function suspend(bytes32 bindingId, bytes32 reasonRef) external; function reinstate(bytes32 bindingId) external; function revoke(bytes32 bindingId, bytes32 reasonRef) external; function readmit(bytes32 bindingId, bytes calldata predicate, bytes[] calldata evidence) external; function confirmReadmission(bytes32 bindingId) external; function evaluate(bytes32 channelId, bytes32 subject, uint16 role, bytes32 scopeRef, uint48 timepoint) external view returns (uint8 outcome); function evaluateNow(bytes32 channelId, bytes32 subject, uint16 role, bytes32 scopeRef) external view returns (uint8 outcome); function rolesOf(bytes32 channelId, bytes32 subject, bytes32 scopeRef, uint48 timepoint) external view returns (uint16 roles); function isPending(bytes32 bindingId, uint48 timepoint) external view returns (bool); function bindingOf(bytes32 bindingId) external view returns (RoleBinding memory); function bindingIdsOf(bytes32 channelId, bytes32 subject) external view returns (bytes32[] memory); } ``` #### 9.1 Role bindings Every grant and every admission creates one role binding with a fresh `bindingId`. The binding records the subject, an optional represented principal (`onBehalfOf`), the role bits, the scope (`bytes32(0)` for the whole channel), the basis kind - 0 manager grant, 1 allowlist, 2 predicate (admission path), 3 decision, 4 external evidence - with its reference, the outcome obtained when it was granted, the paradigm in force when it was granted (`ruleVersion`), the validity interval `[validFrom, validUntil)` with `validUntil == 0` meaning no end, and its state. Suspension and revocation address a binding, never a subject in general: the same subject can be able to submit in one scope and be suspended in another. An implementation MUST keep enough history (suspension intervals and admission epochs with their timepoints) for `evaluate` at a past timepoint to return the same outcome later as it did at the time. #### 9.2 Manager path `grant` is callable by the channel authority, by a subject whose `MANAGE_ADMISSION` binding covers the requested scope (a whole-channel binding covers every scope; a scoped binding covers only its scope), or by an executor with the participation effect. `roles` MUST be a non-empty set of defined role bits; `basisKind` MUST NOT be 2 (the predicate basis is reserved for the admission path). `validFrom` of zero means the current clock value; a `validFrom` earlier than the current clock value MUST be rejected (grants are never back-dated); a non-zero `validUntil` MUST be later than `validFrom`. The channel MUST be `Active`. #### 9.3 Admission path `admit` lets a subject acquire a single role by satisfying the paradigm's predicate for that role. The caller's subject reference is derived from `msg.sender` (Section 3). The registry MUST reject the call when the current paradigm has no predicate hash for the role, when `keccak256(predicate)` differs from that hash, or when the channel is not `Active`. The predicate is evaluated through the deployment's composite verifier with the supplied evidence. On `SATISFIED` a binding with basis kind 2, `basisRef` equal to the predicate hash and `validFrom` equal to the current clock value is created and `AdmissionEvaluated` is emitted. On any other outcome the call MUST revert with an error naming that outcome (`AIFP_NotSatisfied`, `AIFP_Undeterminable`, `AIFP_VerifierError`), so that no record of a failed admission is written that could be read as `NOT_SATISFIED`. `previewAdmission` performs the same evaluation for any subject without state change. #### 9.4 Suspension, revocation, re-admission - `suspend` opens a suspension interval at the current clock value and records who imposed it; it is callable by the parties permitted to grant in the binding's scope (Section 9.2). `reinstate` closes the current interval and MUST be callable only by the party that imposed it: an intervener's suspension is not lifted by the channel authority, and an authority's suspension is not lifted by an intervener. Suspension intervals are kept as history. - `revoke` sets `revokedAt` to the current clock value, closes any open suspension interval and makes the binding `Revoked`; revocation is terminal. It is callable by the parties permitted to grant in the binding's scope. - A binding is **pending** at timepoint `t` when `readmissionEpochAt(channelId, t)` exceeds the greatest epoch recorded for the binding at or before `t`. `readmit` re-evaluates a predicate-based binding against the current paradigm's predicate for its role, callable only by the subject itself, and records a new admission epoch on `SATISFIED` (reverting as in Section 9.3 otherwise). `confirmReadmission` records a new admission epoch for a non-predicate binding and is callable by the parties permitted to grant in its scope. Both update `ruleVersion` to the paradigm in force at that time. #### 9.5 Evaluation `evaluate(channelId, subject, role, scopeRef, t)` MUST be computed as follows. For each role binding of the subject in the channel, the binding is *not applicable* if the role bit is not set, if its scope is neither zero nor `scopeRef`, if `t < validFrom`, if `validUntil != 0 && t >= validUntil`, or if `revokedAt != 0 && t >= revokedAt`. An applicable binding has status `NOT_SATISFIED` if `t` lies within one of its suspension intervals `[from, to)` (`to` open while the suspension is in force); otherwise `UNDETERMINABLE` if the binding is pending at `t`; otherwise `SATISFIED`. The result is `SATISFIED` if any applicable binding is `SATISFIED`; otherwise `UNDETERMINABLE` if any is `UNDETERMINABLE`; otherwise `NOT_SATISFIED` (including when the subject has no applicable binding). `evaluateNow` is `evaluate` at the current clock value. `rolesOf` returns the union of the role bits of the bindings with status `SATISFIED` at `t` for the given scope; it is a summary and MUST NOT be the sole basis for authorizing an action - authorization checks use `evaluate` on the role being exercised. ### 10. Historical validity and admission Rules are applied to the interaction process, not to isolated messages. Two timepoints are distinguished: - **Historical evaluation.** An event already accepted into a channel at timepoint `t` under paradigm `paradigmAt(channelId, t)` remains valid as a historical fact however late it is delivered: later revocations, suspensions, rule changes and re-admission requirements do not change `evaluate(..., t)` for `t` before they took effect. - **Admission of new events.** An event first offered for acceptance now MUST be evaluated at the current clock value, not at a sender-asserted earlier timepoint. A transport binding MUST NOT accept a back-dated `timeRef` as the basis of a permission unless it holds evidence, recognized by the binding, that the event was accepted at that earlier time (for example an anchored event reference of Section 13.1 or its own delivery record). The on-chain `admit`, `grant` and `readmit` functions use the current clock value for this reason. - **Rule version.** An event is evaluated under the paradigm in force at its timepoint. After an adoption's `effectiveAt`, the admission path accepts only the new version's predicate hash; a predicate of a superseded version is rejected with a mismatch. A superseded version remains the correct basis for historical evaluation of events before `effectiveAt`. - **Re-admission.** When an adoption requires re-admission, bindings granted before it are pending from `effectiveAt` and evaluate to `UNDETERMINABLE` until re-admitted or confirmed; they are neither revoked nor treated as `NOT_SATISFIED`. ### 11. Disclosure monotonicity A disclosure that has occurred is a fact: for every information object the set of subjects that have observed it is non-decreasing in time. Consequently: - A rule change or a `RESTRICTED` record MUST NOT be interpreted as the object never having been disclosed. `DISCLOSED` records are never removed; a later `RESTRICTED` record for the same object and audience is an additional fact, not a correction. - Future observation rights, key distribution and content provision MAY contract, including for existing objects: a channel that moves from open observation to a closed roster may stop serving earlier objects to new requests. Contraction does not prove that any existing recipient's copy was deleted, and MUST NOT be presented as such. - Opening a channel to new observers does not by itself extend the disclosure of existing objects: opening future discussion and disclosing existing objects are two independent actions, and the latter leaves one `DISCLOSED` record per object or declared object set. - A `supersedes` or `retracts` relation preserves history and basis: the superseded or retracted object stays referenced and its earlier observation stays a fact, while its content need not remain readable. - A `DISCLOSED` record declares that an audience was authorized to observe; it does not prove that the audience read the object. Recording a migration proves that the rule or state record changed; key rotation, access control and transport capability changes are implementation steps outside the registry. ### 12. Transport binding capabilities A transport binding declares the capabilities of the system it attaches, and a paradigm declares the capabilities it requires under `requiredBindings.capabilities`. A channel whose declared transport bindings do not cover a required capability MUST be rejected at validation or marked `UNDETERMINABLE` by consumers; a binding MUST NOT silently degrade a required capability. | Bit | Capability | Question answered | | --- | --- | --- | | 1 | `identity` | can the binding verify the sender's subject reference | | 2 | `rule-association` | can messages carry `channelId` and `ruleVersion` | | 4 | `push` | push delivery | | 8 | `pull` | pull delivery | | 16 | `stream` | streaming delivery | | 32 | `batch` | batched or epoch delivery | | 64 | `delivery-records` | does the binding record who actually received | | 128 | `history-retention` | does the binding retain history | | 256 | `late-join-history` | can a late joiner read earlier objects | | 512 | `payload-protection` | is the payload protected end to end | | 1024 | `metadata-protection` | is metadata (membership, frequency, size) protected | | 2048 | `membership-privacy` | is the member list hidden from non-members | | 4096 | `total-order` | does the binding sequence contributions totally | | 8192 | `absence-detection` | can the binding detect that an expected event did not occur | Candidate transports include agent task protocols, tool protocols, matrix-style rooms, federated activity feeds, signed-event relays, encrypted group messaging, wallet-to-wallet messaging, decentralized pub/sub and on-chain events. This specification does not define any of them. ### 13. Optional profiles #### 13.1 Event anchor (`aifp:profile/event-anchor/v1`) ```solidity interface IAIFPEventAnchor { struct Relation { uint8 kind; bytes32 objectRef; } struct EventRecord { bytes32 channelId; bytes32 subject; uint8 action; bytes32 objectRef; bytes32 ruleVersion; uint48 at; } event EventAnchored(bytes32 indexed channelId, bytes32 indexed subject, bytes32 indexed objectRef, uint8 action, bytes32 ruleVersion, uint48 at, uint256 recordIndex); event RelationAnchored(bytes32 indexed objectRef, uint8 kind, bytes32 indexed relatedRef); function anchor(bytes32 channelId, uint8 action, bytes32 objectRef, Relation[] calldata relations) external returns (uint256 recordIndex); function record(uint256 recordIndex) external view returns (EventRecord memory); function anchoredBy(bytes32 objectRef) external view returns (bytes32 subject); function recordCount() external view returns (uint256); } ``` Actions are 0 publish, 1 submit, 2 respond, 3 modify, 4 forward, 5 derive, 6 synthesize, 7 supersede, 8 retract. Relation kinds are 0 `replyTo`, 1 `forwards`, 2 `derivesFrom`, 3 `synthesizes`, 4 `references`, 5 `supersedes`, 6 `retracts`. An anchor MUST check at the current clock value that the actor holds the role the action needs (`SUBMIT` for publish, submit, supersede and retract; `RESPOND` for respond; `MODIFY` for modify; `REDISTRIBUTE` for forward, derive and synthesize) and MUST record `paradigmAt(channelId, now)` as the rule version. A forward MUST keep the object reference unchanged, MUST carry a `forwards` relation and MUST refer to an already anchored object; a derivation or synthesis anchors a new object and MUST carry a `derivesFrom` or `synthesizes` relation; supersede and retract MUST be accepted only from the subject that anchored the target object. An anchor is a declaration at a time: it proves neither delivery nor truth, and a chain of forwards adds propagation, not independent evidence. #### 13.2 Absence semantics and objection windows (`aifp:profile/objection-window/v1`) Where a paradigm declares `time.absence` as `rule-defined`, the absence of an event has meaning only inside an authoritative window agreed in advance: a window declares the channel, the proposal, the objection role, a start not earlier than its opening, a deadline and the default consequence. An objection MUST be registered by a holder of the objection role inside `[start, deadline)`. After the deadline, resolution yields the default consequence when no valid objection was registered and the window's integrity is confirmed (it was open for its whole duration and the channel was active throughout), `objected` when one was, and `UNDETERMINABLE` when integrity cannot be confirmed. The default consequence proves that the agreed condition occurred; it proves neither that any participant read the proposal nor that anyone consented. A liveness rule is handled the same way: a missing heartbeat yields only "expired or marked inactive under the rule". #### 13.3 Committed subjects (`aifp:profile/committed-subject/v1`) A role binding MAY record a commitment `keccak256(subjectRef || salt)` in place of a subject reference, together with the proof rules under which the subject later demonstrates membership. The plain subject reference of Section 3 is not such a commitment. This profile defines no on-chain interface in this version. #### 13.4 Receive-policy commitment (`aifp:profile/receive-policy/v1`) A receiver MAY commit to the hash and version of the policy it applies when deciding whether to process what it is permitted to read. The three gates - the channel permits the publisher to publish, the receiver is permitted to read, the receiver chooses to process - are independent, and this profile only lets the third be demonstrated after the fact. This specification defines no universal trust score and no handling hint that could lower the protection a receiver requires. ### 14. Namespace Identifiers use the `aifp:` namespace: `aifp:paradigm//v` (labels, Section 5.2), `aifp:op/`, `aifp:dim/`, `aifp:pred/`, `aifp:rel/`, `aifp:handling/`, `aifp:profile//v` and `aifp:ext///v` for third-party extensions. ### 15. Interface detection Every registry, the composite verifier and the event anchor MUST implement [ERC-165](./eip-165.md) and report the identifier of the interface it implements: ```solidity interface IERC165 { function supportsInterface(bytes4 interfaceId) external view returns (bool); } ``` The interface identifiers are listed in the Test Cases. ## Rationale **A generative model instead of an enumeration.** The familiar forms of human communication (broadcast, panel, assembly, meeting, three kinds of peer-to-peer) are kept, but as labels on configurations rather than as types. Two carriers, eight operators and thirteen separately declared dimensions generate those forms and the agent-native ones (intake, blackboard, dispatch, forward, derive) alike, and a new form is first expressed with the existing vocabulary before anything is added. Completeness is argued by closure under composition, explicit compatibility rules, a counterexample list and an open extension namespace, not by claiming that the Cartesian product of the dimensions is exhaustive. Identification conditions are deliberately decidable from the descriptor alone, so that a validator can check them and the registry does not have to; where two conditions overlap, a descriptor simply carries both labels. **Why the dimensions are split the way they are.** Earlier drafts merged "how many" with "how admitted", "synchronous" with "request-response", "relationship" with "proof", and "end-to-end encryption" with "metadata hidden". Each pair is a different question with independent answers: two participants can form an open club; a request-response exchange can be asynchronous; a stranger can present a valid credential; an encrypted group still leaks its membership and message frequency. Splitting them is what lets a paradigm say exactly which guarantee it offers. **Rules constrain processes, not isolated messages.** Whether a message is acceptable depends on state (was the sender's role revoked, was a one-time authorization already used, is the epoch quota exhausted, which version was in force) and on a clock. The three receiver sets - addressed, permitted, actually received - are kept apart because a public reply addressed to one person is read by thousands, and because a published archive has no determinate audience when it is published. Evaluation is therefore a function of registry state at a timepoint, and the registry keeps suspension intervals and admission epochs as history so that the function is stable. **Separately addressable role bindings.** A subject-level permission set cannot express "may submit in lane X, suspended in lane Y", nor can it be suspended by an intervener without also being liftable by the channel's own managers. Each grant therefore produces a `bindingId` with its own scope, basis, interval and state; `evaluate` takes a scope; suspension records who imposed it and only that party lifts it; `rolesOf` survives as a summary that is never the sole basis of an authorization. `authorizationRef` fields everywhere are references to evidence and confer nothing by themselves, which is why the call-permission tables are explicit. **Two acquisition paths.** The manager path records what an authority asserts; the admission path records what a predicate proved. They are distinguished by basis kind so that a reader of the registry can tell a roster entry from a satisfied condition, and so that a predicate binding can be re-proved after a rule change while a roster binding is confirmed by a manager. **Four-valued outcomes.** A verifier that timed out, an unknown required extension and a missing proof are not evidence that a subject is unqualified. Folding them into "not satisfied" would record a false negative that later readers cannot distinguish from a real one; the admission path reverts with the obtained outcome instead of writing anything. The composition tables give `NOT_SATISFIED` precedence in `allOf` and `SATISFIED` precedence in `anyOf` because those are the only definitive answers; among the non-definitive ones, `UNDETERMINABLE` outranks `VERIFIER_ERROR` because an unverifiable condition is a property of the request, while a verifier error may be transient. Negation is omitted because `not(UNDETERMINABLE)` is still undeterminable and the operator invites "could not verify, therefore unqualified". The leaf-call rule of Section 6.3 exists because the promise "a faulty verifier is that leaf's error" is only as good as the mechanism that keeps the fault inside the leaf: a typed external call decodes its return value in the caller, so a verifier that returns no data or a word outside the declared type would revert the whole evaluation - and, through the registry's own error handling, turn `anyOf(allowlist, external)` into `VERIFIER_ERROR` for a subject the allowlist admits. Checking the raw return data and bounding the gas of each leaf call are what make the composition tables hold. **Historical validity versus admission.** "Evaluate under the rules in force at the event's timepoint" and "reject old-version events after a migration" are reconciled by distinguishing re-evaluation of already accepted events from first-time admission: the former uses the historical timepoint, the latter the current clock value, and a back-dated timepoint is admissible only with binding-recognized evidence of earlier acceptance. Grants are never back-dated for the same reason. **One clock per deployment.** Timepoints appear in adoptions, intervals, disclosures and evaluations; mixing block numbers and timestamps across registries would make `paradigmAt` and `evaluate` ambiguous. ERC-6372 is a true dependency because the registries expose `clock()` and `CLOCK_MODE()` and every timepoint is read on them; it is listed in `requires`. Ties are broken by recording order so that two changes at one timepoint have a determinate outcome. **Disclosure monotonicity as a fact about history.** The only non-invertible operation in the model is disclosure: every other action has an inverse (join/leave, grant/revoke, commit/reveal, push/pull). The specification therefore forbids rewriting the fact that something was disclosed, while allowing future provision to contract - including for existing objects - because a registry can stop serving but cannot delete copies, and a specification that pretended otherwise would mislead. `DISCLOSED` and `RESTRICTED` records are both facts and both append-only. **What the registry does and does not validate.** Verifying that a Panel's observation scope contains its contribution scope is undecidable on-chain when both are arbitrary predicates. The split is therefore: the deterministic validator checks format, compatibility rules and the finite identification conditions; the contract checks hashes, family control, succession, state and the outcomes of the verifiers it calls; `hasLabel` means "registered", nothing more. **One subject encoding.** Addresses, agent registry identifiers, DIDs and external identifiers are hashed from one canonical string with explicit scheme and domain, so that an `erc8004` reference names its chain and registry and an address names its chain. Two parallel encodings (left-padded addresses and hashed strings) would make the same actor appear twice. A plain hash is not a privacy commitment; committed subjects are a profile with their own proof rules. **Absence semantics.** A clock is not enough to prove that nobody objected, because observers may have missed the proposal. The objection-window profile requires a window agreed in advance, an objection role, a registration place, a deadline and an integrity condition, and returns `UNDETERMINABLE` when integrity cannot be confirmed. Its default consequence is the occurrence of an agreed condition, never a proof of consent. **Relationship to neighbouring work.** Agent identity, reputation and validation registries (ERC-8004), know-your-agent credentials, agent identity anchors, collective decision registries (ERC-8436), resource discovery ([ERC-8444](./eip-8444.md)), swarm registries, safety specification bindings, dispute resolution registries, task tenders and economic unit registries are attached through predicate leaves and governance or economic bindings. None of them is required to implement this ERC, and this ERC defines none of them. Transports are attached through transport bindings with declared capabilities and are not standardized here. **Counterexamples recorded during design.** A panel with one speaker at the moment is not a source unless the rules allow only one contributor; a two-person meeting is a private direct link but says nothing about acquaintance; a club need not be synchronous; a referred peer-to-peer link need not be semi-public; a static web page is a source plus an archive with pull delivery, not the opposite of a source; moderators, capacity limits and speaking order do not decide between panel and club - the acquisition mechanism does; a dating application is a selection market, not open peer-to-peer; a venue where agents contribute and humans observe is one context with role-conditioned boundaries, not two paradigms; stranger, referral and prior link are not three mandatory stages of a link; and a chain of forwards adds propagation, not independent evidence. ## Backwards Compatibility This ERC introduces new interfaces and does not modify existing ones. It composes with ERC-165 for interface detection and ERC-6372 for clocks (both linked above). Registries of agent identity, credentials, decisions, discovery, swarms, safety and disputes remain independent and are referenced by subject references, predicate leaves and binding declarations only; an implementation of this ERC does not have to implement any of them, and they do not have to know about it. ## Test Cases All vectors below are reproduced by the reference tools and checked by the reference test suite; the complete sets are the [subject reference](../assets/eip-8489/vectors/subject-refs.json), [predicate encoding](../assets/eip-8489/vectors/predicate-encoding.json), [outcome table](../assets/eip-8489/vectors/outcome-tables.json), [interface identifier](../assets/eip-8489/vectors/interface-ids.json) and [paradigm identifier](../assets/eip-8489/vectors/paradigm-ids.json) vector files. ### Subject references | Canonical string | Reference | | --- | --- | | `eip155-address:1:0x0000000000000000000000000000000000000001` | `0x3e4e98887f2ee7b0b3a5a889a1a8a1d840c24613f5cbf173aa7119b38e1b0736` | | `eip155-address:11155111:0xabcdef0123456789abcdef0123456789abcdef01` | `0x2db485f9087d3732d84788a340a04ed0f0c2024e1c678b370da87edab612b7e3` | | `erc8004:11155111:0x8004a818bfb912233c491871b3d84c89a494bd9e:10387` | `0xf5c6492537a52dd2ba828b13bfbc7196294e0378a818fa9088e041684ec8fd28` | | `did:aid:example-7` | `0x712b940069fe06c353d1e338f4c3ab958182ce536199add570040bc624db5f41` | | `ext:finchip.ai:foga-cell-7` | `0xa5686f86afff8c2bc351b4ff80cb33c9e8d2852336b0057ca0c51394a827e837` | The mixed-case input `0xAbCDef0123456789aBCDeF0123456789AbCdEf01` canonicalizes to the lowercase form in the second row. ### Predicate encodings The single node `{open: true}` encodes as `abi.encode([Node(0, 0, 0, address(0), bytes32(0), "")])`, that is ```text 0x0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000c0 0000000000000000000000000000000000000000000000000000000000000000 ``` and its hash is `0xd9748b88542c28c751ee2cfb24a3b2ab8d9ce5e6f55c9df01f4a1a4bbf53a91e`. `{allowlist: {set: 0x0000000000000000000000000000000000000001}}` hashes to `0xab7712d47464ad123b4b508fd749b08607b2e31cacca600d5a5ae06f61a111c9`. `{allOf: [{credential: {verifier: 0x..01, scheme: 0xc3d44c83..6764, minLevel: 1, proofMode: attested}}, {identity: {verifier: 0x..02, registry: 0x..03, requiredState: active}}]}` encodes as three nodes - node 0 `allOf` with `childStart 1, childCount 2`; node 1 `credential` with ref `0xc3d44c830a5c118f980f48317b97e9eaa920a7bbd204c7d3595ac191d4726764` and params `abi.encode(uint256 1, uint8 0, uint256 0, address[] [])`; node 2 `identity` with ref `0x0000000000000000000000000000000000000000000000000000000000000003` and params `abi.encode(uint8 1)` - and hashes to `0x28b6f9620d46b8c2a90d9d0478093e46389160cee5efa63671f26daf04aa6ffc`. A nested predicate `allOf(allowlist, anyOf(open, external), anyOf(member, allOf(stake, decision)))` allocates breadth-first as nodes `0 allOf(1,3)`, `1 allowlist`, `2 anyOf(4,2)`, `3 anyOf(6,2)`, `4 open`, `5 external`, `6 member`, `7 allOf(8,2)`, `8 stake`, `9 decision`, where `(s,n)` is `(childStart, childCount)`. ### Outcome composition | left | right | allOf | anyOf | | --- | --- | --- | --- | | SATISFIED | SATISFIED | SATISFIED | SATISFIED | | SATISFIED | NOT_SATISFIED | NOT_SATISFIED | SATISFIED | | SATISFIED | UNDETERMINABLE | UNDETERMINABLE | SATISFIED | | SATISFIED | VERIFIER_ERROR | VERIFIER_ERROR | SATISFIED | | NOT_SATISFIED | NOT_SATISFIED | NOT_SATISFIED | NOT_SATISFIED | | NOT_SATISFIED | UNDETERMINABLE | NOT_SATISFIED | UNDETERMINABLE | | NOT_SATISFIED | VERIFIER_ERROR | NOT_SATISFIED | VERIFIER_ERROR | | UNDETERMINABLE | UNDETERMINABLE | UNDETERMINABLE | UNDETERMINABLE | | UNDETERMINABLE | VERIFIER_ERROR | UNDETERMINABLE | UNDETERMINABLE | | VERIFIER_ERROR | VERIFIER_ERROR | VERIFIER_ERROR | VERIFIER_ERROR | The tables are symmetric; a value of 7 normalizes to VERIFIER_ERROR; an empty list is rejected. ### Interface identifiers | Interface | Identifier | | --- | --- | | `IAIFPParadigmRegistry` | `0xa30ca0da` | | `IAIFPChannelRegistry` | `0x94dbe6d5` | | `IAIFPParticipationRegistry` | `0x14c97260` | | `IAIFPCompositeVerifier` | `0xa5913d6e` | | `IAIFPPredicateVerifier` | `0xe7a501b7` | | `IAIFPSubjectSet` | `0x1d1a696d` | | `IAIFPEventAnchor` | `0x62b24396` | ### Paradigm identifiers The canonical encodings of the example descriptors ([source](../assets/eip-8489/examples/paradigms/source.json), [panel](../assets/eip-8489/examples/paradigms/panel.json), [club](../assets/eip-8489/examples/paradigms/club.json), [intake](../assets/eip-8489/examples/paradigms/intake.json), [blackboard](../assets/eip-8489/examples/paradigms/blackboard.json)) hash to: `source` `0xea944ef3ca51ac0aa41ddb93ea4b1f940c1637633bb116d20a9f896bb8c5d3f8`, `panel` `0x05e65109a6abf73cdec4422cc754a69b1aec753041df036a9fd3c7099775bf66`, `club` `0x0a88feca81ef7365bf30b3457ea225e8e10d139bdb5e21efd126f39b8f2d7420`, `intake` `0x9ab153e21196d42fabe497ca76c0bfa82f2bfc0f001d6af2caee172243aa7694`, `blackboard` `0x1a4da67556be4c71967131eb983d083e9f2f3930f3587b76e99de2d528a0bf19`. The `panel` example declares the `observe` role with the `open` predicate, so `rolePredicateHash(paradigmId, 2)` is `0xd9748b88542c28c751ee2cfb24a3b2ab8d9ce5e6f55c9df01f4a1a4bbf53a91e`. ### Evaluation scenarios Let the clock be `t0` at channel opening and let `A` be the channel authority. 1. `A` grants `B` role 2 (`OBSERVE`) at `t0`. `evaluate(B, 2, 0, t0)` is `SATISFIED`; `evaluate(B, 4, 0, t0)` is `NOT_SATISFIED`; `evaluate(B, 2, 0, t0 - 1)` is `NOT_SATISFIED`. 2. `A` revokes that binding at `t1 > t0`. `evaluate(B, 2, 0, t1 - 1)` stays `SATISFIED`; `evaluate(B, 2, 0, t1)` is `NOT_SATISFIED`. 3. `A` adopts a new paradigm version with `requiresReadmission` true and `effectiveAt = t2`. A binding granted at `t0` evaluates `SATISFIED` at `t2 - 1` and `UNDETERMINABLE` at `t2`; after `readmit` or `confirmReadmission` at `t3 >= t2` it evaluates `SATISFIED` at `t3` and still `UNDETERMINABLE` at any timepoint in `[t2, t3)`. 4. After `t2`, `admit` with the predicate bytes of the superseded version reverts with a predicate mismatch; with the new version's predicate bytes it succeeds when the predicate is `SATISFIED`. 5. `C` holds a scoped `MANAGE_ADMISSION` binding for scope `X`. `C` can grant in scope `X` and cannot grant in scope `Y` or for the whole channel. 6. An intervener (executor with the participation effect) suspends `B`'s binding. `A` cannot reinstate it; the intervener can. While suspended, another `SATISFIED` binding of `B` for the same role and scope still makes `evaluate` return `SATISFIED`. 7. `recordDisclosure(kind 0)` for object `o` to audience `p`, followed by `recordDisclosure(kind 1)` for the same pair, yields two records; `disclosureCount` is 2 and the first record is unchanged. 8. A composite verifier given a tree containing kind 42 returns `UNDETERMINABLE` without calling any verifier; given a delegated leaf whose verifier address has no code returns `VERIFIER_ERROR` for that leaf. 9. A delegated leaf whose verifier returns no data, a word of value 256, 16 bytes, or two words is `VERIFIER_ERROR` for that leaf, and so is an `allowlist` leaf whose set returns the word 2; `anyOf(allowlist, external)` with the subject in the allowlist and such a verifier in the external leaf is `SATISFIED`, `allOf` of the same two leaves is `VERIFIER_ERROR`, and the admission path admits the subject that is in the allowlist. A delegated leaf that consumes all the gas it is given is `VERIFIER_ERROR` for that leaf, and `anyOf(external, open)` with that leaf is `SATISFIED`. ## Reference Implementation The reference implementation consists of [AIFPParadigmRegistry](../assets/eip-8489/contracts/AIFPParadigmRegistry.sol), [AIFPChannelRegistry](../assets/eip-8489/contracts/AIFPChannelRegistry.sol), [AIFPParticipationRegistry](../assets/eip-8489/contracts/AIFPParticipationRegistry.sol), [AIFPCompositeVerifier](../assets/eip-8489/contracts/AIFPCompositeVerifier.sol), [AIFPSubjectSet](../assets/eip-8489/contracts/AIFPSubjectSet.sol), the optional [AIFPEventAnchor](../assets/eip-8489/contracts/AIFPEventAnchor.sol), the companion [AIFPObjectionWindow](../assets/eip-8489/contracts/companions/AIFPObjectionWindow.sol), and the libraries [AIFPRoles](../assets/eip-8489/contracts/libraries/AIFPRoles.sol), [AIFPOutcomes](../assets/eip-8489/contracts/libraries/AIFPOutcomes.sol), [AIFPSubject](../assets/eip-8489/contracts/libraries/AIFPSubject.sol), [AIFPPredicates](../assets/eip-8489/contracts/libraries/AIFPPredicates.sol), [AIFPBindings](../assets/eip-8489/contracts/libraries/AIFPBindings.sol), [AIFPBasis](../assets/eip-8489/contracts/libraries/AIFPBasis.sol) and [AIFPEvents](../assets/eip-8489/contracts/libraries/AIFPEvents.sol). The [assets index](../assets/eip-8489/README.md) lists the schemas, example descriptors, vectors and tools. ## Security Considerations **Low marginal cost of identities.** An open or predicate-acquired role invites many subjects controlled by one party. A stake or credential condition raises the cost of a specific behaviour and ties admission to an external fact; it does not show that several subjects belong to independent controllers. Paradigms that rely on counts of distinct subjects (quorums, votes, objection windows) are exposed to this and are expected to combine conditions, for example a credential with a stake, rather than rely on one. **Content is never authority.** A message in a channel can contain an instruction; a subject with execution capabilities that treats received content as a command is exposed to injection whatever the channel's paradigm. Role bindings in this ERC authorize information actions - observe, submit, respond, modify, redistribute - and nothing else; the authority to execute an action in the world comes from a separate authorization mechanism, and handling hints supplied by a sender cannot lower the protection a receiver applies. **Declaration, enforcement and evidence are three different things.** A registry record proves that a rule version, a role binding or a disclosure record existed at a time. It does not prove that a transport enforced the rule, that keys were rotated when observation contracted, or that off-chain behaviour followed the rules; those need their own evidence. Readers of the registry are advised to treat `hasLabel` as a declaration and the capability bits of a transport binding as the binding's own claim. **Verifier liveness and misbehaviour.** A leaf verifier that reverts, runs out of the gas it is given, returns data that is not one outcome word, returns an out-of-range value or has no code produces `VERIFIER_ERROR` for its leaf and for nothing else; a verifier that lies produces a wrong definitive outcome. The leaf-call rule of Section 6.3 is what keeps a fault inside its leaf: without it a single broken or malicious verifier in an `anyOf` could deny admission to subjects that another branch admits, and a verifier that burns gas could abort the whole evaluation. The gas bound also caps the work a leaf verifier may do, so paradigm authors are expected to choose verifiers that answer within it, and callers to supply enough gas for the leaves a predicate contains. Paradigms choose their verifiers, and the predicate hash commits to the verifier addresses, so a channel's admission conditions change only with a new paradigm version. A verifier that is upgradeable behind a proxy weakens that commitment. **Timepoints supplied by callers.** The registries take `effectiveAt`, `validFrom` and `validUntil` from callers and reject values in the past, but a caller can schedule changes far in the future; consumers reading `paradigmAt` and `evaluate` at future timepoints see those scheduled changes. Sender-asserted timepoints in transports are not accepted as a basis for permissions (Section 10). **Disclosure contraction is not deletion.** Moving observation from open to closed, or recording a `RESTRICTED` disclosure, changes future provision only. Anyone who observed an object before the change may retain and republish it, subject only to the redistribution rules they accepted and to mechanisms outside this ERC. **Metadata.** Membership lists, binding scopes, adoption times, disclosure records and anchored event references are public on-chain unless the committed-subject profile or an off-chain transport with membership privacy is used. Paradigms that promise anonymity or metadata protection depend on their transport bindings for it; the registry alone offers none. **Governance capture.** An executor named in an automatic governance binding can change rules, participation or disclosure records within its declared effect scope without further confirmation. Channels that attach a decision or safety registry are advised to prefer the confirmed adoption mode for effects that cannot be undone, and to keep the rules effect behind a decision process whose participants are themselves subject to admission conditions. **Absence semantics.** A default consequence triggered by an objection window proves that no valid objection was registered in a complete window. It does not prove that participants read the proposal, agreed with it, or were able to object; a frozen channel or an unreachable transport during the window makes the resolution undeterminable rather than silent consent. **Resource limits.** Evaluation iterates over a subject's bindings in a channel (capped at 64 per subject and channel in the reference implementation) and over the channel's adoptions. Paradigms with very frequent re-adoption, or managers that grant many bindings to one subject, raise the gas cost of `evaluate` for everyone who checks that subject. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).