Skip to content

docs(BOP-509): document composite policy support in PolicyRegistry - #194

Open
rayyan224 wants to merge 3 commits into
mainfrom
rayyanalam/bop-509-keep-policyregistry-docs-in-sync-with-composite-policies
Open

docs(BOP-509): document composite policy support in PolicyRegistry#194
rayyan224 wants to merge 3 commits into
mainfrom
rayyanalam/bop-509-keep-policyregistry-docs-in-sync-with-composite-policies

Conversation

@rayyan224

Copy link
Copy Markdown
Collaborator

Summary

  • docs/PolicyRegistry/README.md still described UNION/INTERSECT composition as a "planned for a future hardfork" feature, but createCompositePolicy, updateComposite, and compositePolicyChildIds already ship in IPolicyRegistry (see src/interfaces/IPolicyRegistry.sol, test/unit/PolicyRegistry/*Composite*.t.sol).
  • Rewrote the Policy Types section to cover simple vs. composite policies, including the 2–4 child-count bound and the "no composite-of-composite / no built-in child" rules.
  • Added compositePolicyChildIds, MIN_COMPOSITE_CHILD_POLICIES, MAX_COMPOSITE_CHILD_POLICIES to the always-callable list, and createCompositePolicy/updateComposite to the gated-writes list.
  • Added "Create Composite Policy" and "Update Composite Children" user-flow sections (sequence diagrams + revert conditions), mirroring the existing simple-policy flows.

Source: BOP-509 — flagged by @connerswenberg in Slack as the docs missing composite policy coverage. The ticket also floats a doc/src-diff-detecting harness as a follow-up; scoping that separately rather than bundling it into this docs fix.

Test plan

  • Docs-only change; no code/behavior touched.
  • Cross-checked every new claim against src/interfaces/IPolicyRegistry.sol and test/unit/PolicyRegistry/{createCompositePolicy,updateComposite,compositeChildPolicyLimits,compositePolicyChildIds,isAuthorized}.t.sol.

The PolicyRegistry docs still described union/intersect composition as a
future hardfork feature, but createCompositePolicy/updateComposite/
compositePolicyChildIds already ship in IPolicyRegistry. Bring the docs back
in line with the interface and test suite: new UNION/INTERSECT policy type
section, updated activation gating lists, and Create/Update Composite user
flows with their revert conditions.

Co-Authored-By: Claude <noreply@anthropic.com>
@linear

linear Bot commented Aug 7, 2026

Copy link
Copy Markdown

BOP-509

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Interface Coverage

✅ All interface functions have test coverage.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

📊 Forge Coverage (src/lib/)

🟡 ≥95% across all metrics — some metrics below 99%.

File Lines Stmts Branches Funcs
🔴 B20FactoryLib.sol 95.40% 96.00% 100.00% 90.00%
🔴 test/lib/ForceFeeder.sol 0.00% 0.00% 100.00% 0.00%
🔴 test/lib/PrecompileProbe.sol 0.00% 0.00% 0.00% 0.00%
🟢 MockActivationRegistry.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockActivationRegistryStorage.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20Asset.sol 100.00% 100.00% 100.00% 100.00%
🟡 MockB20Factory.sol 98.96% 99.10% 100.00% 100.00%
🟢 MockB20Stablecoin.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20Storage.sol 100.00% 100.00% 100.00% 100.00%
🟡 MockPolicyRegistry.sol 100.00% 99.54% 97.67% 100.00%
🟢 MockPolicyRegistryStorage.sol 100.00% 100.00% 100.00% 100.00%
Total 96.79% 97.28% 98.14% 96.49%

Full report: download artifact. To browse locally: make coverage (runs forge coverage + genhtml + opens the HTML report).

Comment thread docs/PolicyRegistry/README.md Outdated
- **`UNION`** (OR) — authorized if *any* child policy authorizes the account.
- **`INTERSECT`** (AND) — authorized only if *every* child policy authorizes the account.

A composite's child set is 2–4 existing simple (`ALLOWLIST`/`BLOCKLIST`) policy IDs — never another composite, and never a built-in sentinel (`ALWAYS_ALLOW`/`ALWAYS_BLOCK`). Composites reference their children live: `isAuthorized` reads current child membership on every call, not a snapshot taken at composite-creation time, so updating a child's membership immediately changes what the composite authorizes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just keep it as "Composites reference their children live: isAuthorized reads current child membership on every call. So updating a child's membership immediately changes what the composite authorizes."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread docs/PolicyRegistry/README.md Outdated
PolicyRegistry-->>PolicyAdmin: emit CompositePolicyUpdated(policyId, updater, childPolicyIds)
```

`childPolicyIds` is a full replacement, not a merge — a child omitted from the new set no longer governs the composite, even if it governed before. There is no clear-the-list path: the new set must still satisfy the same size and child-validity rules as creation. Because composites read child membership live, `isAuthorized` reflects the new set immediately.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with

"childPolicyIds is a full replacement, a child omitted from the new set no longer governs the composite. The new set must still satisfy the same size and child-validity rules as creation"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

✅ Fork tests: all 711 passed

base/base is fully in sync with the base-std spec.

- **`UNION`** (OR) — authorized if *any* child policy authorizes the account.
- **`INTERSECT`** (AND) — authorized only if *every* child policy authorizes the account.

A composite's child set is 2–4 existing simple (`ALLOWLIST`/`BLOCKLIST`) policy IDs — never another composite, and never a built-in sentinel (`ALWAYS_ALLOW`/`ALWAYS_BLOCK`). Composites reference their children live: `isAuthorized` reads current child membership on every call. So updating a child's membership immediately changes what the composite authorizes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware we were excluding the sentinels (though that makes sense). I think the interface doc is missing this nuance. From createCompositePolicy in IPolicyRegistry L124

    /// @dev Child policies must be simple policies (ALLOWLIST or BLOCKLIST), never another composite.
    ///      The child-policy set is capped at 4.
    /// @dev Reverts with `IncompatiblePolicyType` when `policyType` is not UNION or INTERSECT.
    /// @dev Reverts with `ZeroAddress` when `admin` is `address(0)`.
    /// @dev Reverts with `ChildPoliciesOutsideOfRange` when `childPolicyIds.length` is not in
    ///      `[MIN_COMPOSITE_CHILD_POLICIES, MAX_COMPOSITE_CHILD_POLICIES]`.
    /// @dev Reverts with `PolicyNotFound` when any child policy does not exist.
    /// @dev Reverts with `InvalidChildPolicy` when any child policy is not a simple policy or a built-in policy.
    /// @dev Panics with arithmetic overflow (Panic 0x11) when the policy counter has reached its maximum value.
    ///

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the top-level @dev line only called out "never another composite" and left the sentinel exclusion to be inferred from the later revert line. Fixed the NatSpec on both createCompositePolicy and updateComposite to spell out the built-in-sentinel exclusion explicitly, matching the revert conditions and the README.

createCompositePolicy/updateComposite already revert InvalidChildPolicy for
ALWAYS_ALLOW/ALWAYS_BLOCK children, but the leading @dev lines only called
out "not another composite" and left the sentinel case implicit. Spell it
out in both, matching the behavior in the composite test suite and the
PolicyRegistry docs.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants