PCL Composite Policies — LogicalPolicy and ForEachPolicy

component compliance

Structural combinators that compose leaf templates into AND / OR trees or fan a single rule over resolved agent owners.

LogicalPolicy and ForEachPolicy are structural policy templates that wrap other PolicySet entries into a tree instead of carrying their own leaf rule. LogicalPolicy combines its children under an AND (Every) or OR (Any) quantifier; ForEachPolicy takes a single child and applies it once per resolved subject — currently the caller's set of agent owners. Both are subject to a maximum structural depth (MaxDepthExceeded) and to a set of validation errors (LogicalPolicyChildrenEmpty, ForEachChildAbsent, QuantifierUnspecified, ChildSelectorNotEmpty) that reject malformed trees at registration.

The structural structs

Straight from IPcl.sol:
enum LogicalQuantifier { Unspecified, And, Or }
enum ForEachQuantifier { Unspecified, Any, Every }
enum ForEachSubject { Unspecified, AgentOwners }

struct LogicalPolicy {
    LogicalQuantifier quantifier;
    PolicySet[] children;
}

struct ForEachPolicy {
    ForEachQuantifier quantifier;
    ForEachSubject subject;
    PolicySet child;
}

LogicalPolicy — AND / OR combinator

LogicalPolicy collects two or more PolicySet children and evaluates them under one quantifier:

  • And — every child must pass. On the first failure PCL surfaces that child's ReasonCode.
  • Or — at least one child must pass. If every child fails, PCL surfaces AnyOfRejected(bytes[] childReverts) carrying the ABI-encoded revert payload of each failed child so the client can unwrap them and pick a user-facing message.


Each child PolicySet inside a LogicalPolicy must have an empty selector — the selector is set once on the enclosing PolicySet. Registering a child with a non-empty selector reverts with ChildSelectorNotEmpty. An empty children array is rejected with LogicalPolicyChildrenEmpty, and a nil entry within the array reverts with LogicalPolicyChildNil(uint256 index). Any leaf template can appear as a child; there is no runtime restriction on which templates may be nested.

ForEachPolicy — fan a rule over a subject set

ForEachPolicy applies its single child policy once per element of a resolved subject set. The only subject implemented today is AgentOwners, which expands to the set of on-chain owners of every agent bound to the caller wallet. The quantifier controls how the per-subject results combine: Every requires all owners to pass, Any requires at least one. Common use case: "at least one of this agent's owners must be attested" — wrap EAS_POLICY in a ForEachPolicy(subject: AgentOwners, quantifier: Any).

Registration validates the tree eagerly: an unset subject reverts with ForEachSubjectUnspecified, an unset quantifier with QuantifierUnspecified, and an absent child with ForEachChildAbsent. An unrecognised subject enum value reverts with UnknownForEachSubject(uint8 subject).
// "At least one of this caller's agent owners must hold the KYC attestation."
EasPolicy memory eas = EasPolicy({
    easContract:   0x1000000000000000000000000000000000000007,
    indexContract: 0x1000000000000000000000000000000000000008,
    schemaUid:     0x5f3a2b0e1d5c9a7f6e3b4a1d2c8f7b6a5e4d3c2b1a0f9e8d7c6b5a4938271605
});

PolicySet memory child = PolicySet({
    templateId: "EAS_POLICY",
    policy:     abi.encode(eas),
    selector:   ""  // required — must be empty on structural children
});

ForEachPolicy memory forOwners = ForEachPolicy({
    quantifier: ForEachQuantifier.Any,
    subject:    ForEachSubject.AgentOwners,
    child:      child
});

Depth limit and diagnostics

Structural policies count against a fixed maximum depth. Exceeding it during registration reverts with MaxDepthExceeded(uint8 maxDepth) — nesting an OR inside an AND inside another OR quickly hits the ceiling. When an Or combinator rejects, the revert data is AnyOfRejected(bytes[] childReverts): iterate childReverts, decodeErrorResult each entry, and decide which one is worth showing to the user. All other structural errors (LogicalPolicyChildrenEmpty, LogicalPolicyChildNil, ForEachChildAbsent, ForEachSubjectUnspecified, QuantifierUnspecified, UnknownForEachSubject, ChildSelectorNotEmpty) are surfaced at changeContractPolicies / setGlobalPolicies time, so a well-formed config cannot fail these at execution.
ESC
Type to search