Skip to content

feat(hats): capability-hat refactor + RoleBundleHatter + election cascade#164

Draft
hudsonhrh wants to merge 3 commits into
mainfrom
hudsonhrh/hat-role-inheritance
Draft

feat(hats): capability-hat refactor + RoleBundleHatter + election cascade#164
hudsonhrh wants to merge 3 commits into
mainfrom
hudsonhrh/hat-role-inheritance

Conversation

@hudsonhrh
Copy link
Copy Markdown
Member

Summary

Replaces the old "array of allowed hats" gating model with one capability hat per atomic action. Each gate becomes a single isWearerOfHat(user, capHat) (~3k gas vs ~12-40k for the old loop+balanceOfBatch model — 77% reduction in the gas benchmark).

A new per-org RoleBundleHatter atomically mints a role hat plus its bundle of capability hats, and a diff-based revokeRole cascade removes role + bundle caps while preserving caps the user still has via other roles they wear (the election-with-fallback case: "Alice loses VP, keeps Member's caps"). Migration is wired through OrgDeployer + EligibilityModule.setAuthorizedRevoker + a migration script with separate sim (ERC1967) and broadcast (BeaconProxy) runners.

Test plan

  • forge build clean
  • forge fmt clean
  • Full local suite: 1270/1270 pass (RoleBundleHatter unit + integration, election scenarios, re-mint after revoke, cap-mask validation, EligibilityModule scope narrowing, MockHats production fidelity)
  • Gas benchmark: 41,925 → 9,662 gas per permission check
  • Fork-RPC DeployerTest suite (was rate-limited locally — re-run in CI)
  • Run migration sim against each live org fork (KUBI / Test6 / Poa) before broadcast

🤖 Generated with Claude Code

hudsonhrh and others added 3 commits May 13, 2026 18:19
Replaces the old "array of allowed hats" gating model with single
capability hats per atomic action. Adds RoleBundleHatter for atomic
role+bundle minting and diff-based revoke cascade that preserves caps
inherited from other roles a user still wears. Includes end-to-end
election scenario tests, migration script for the 3 live orgs, and
gas benchmarks showing ~77% reduction on permission checks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… wins

Reconciles main's TaskManager v4/v5 + EligibilityModule lockdown (#159, #167,
#169) with the capability-hat refactor. Per direction, the capability-hat model
is canonical and main's new features were re-expressed in it.

Resolution highlights:
- TaskManager Layout: kept main's foldersRoot/organizerHatIds in their on-chain
  slots (live-org upgrade safety), appended cap-hat gate fields after, plus new
  budgetHat/editMetaHat/editFullHat. Append-only vs deployed v5 layout.
- v4 editable budgets: BOUNTY_CAP/PROJECT_CAP now gated by _requireBudgetEditor
  -> BUDGET capability hat (executor OR cap-hat), not the bitmask.
- v5 post-claim edits: updateTask (EDIT_FULL) + updateTaskMetadata (EDIT_META)
  gate via _hasCap, not _permMask.
- Folders kept verbatim (orthogonal; organizerHatIds stays a HatManager array gate).
- bootstrapGlobalPerms re-expressed as a deployer-time bulk global gate-hat setter
  (expands each mask into per-gate cap-hat assignments); deleted the dead bitmask
  helpers (_permMask, _syncPermissionHat, refcount machinery).
- setConfig(ROLE_PERM)/setProjectRolePerm extended to BUDGET/EDIT_META/EDIT_FULL,
  kept strict single-bit (InvalidCapMask).
- EligibilityModule: kept #167 superAdmin-only lockdown; the sole exception is
  setWearerEligibility via onlySuperAdminOrRevoker (RoleBundleHatter cascade).
- OrgDeployer: DeploymentParams keeps both capabilityHats/roleBundles and
  taskManagerPerms (complementary). Re-added _resolveRoleIndicesToHatIds.
- Added lens variant 12 ([budgetHat, editMetaHat, editFullHat]) for the new gates.

Security fix surfaced by the merge (pre-existing on the branch; DeployerTest was
previously excluded from verification): RoleBundleHatter.mintRole's pre-mint
eligibility reset unconditionally set wearers eligible, bypassing the vouching
gate (defaults.eligible=false) on self-service QuickJoin. Now gated on
hasSpecificWearerRules so it only re-enables previously-revoked wearers (preserves
re-mint-after-revoke) and never overrides a default-driven gate for fresh wearers.

Tests: 1423 passing / 0 failing / 16 skipped (obsolete bitmask tests). Main-era
bitmask tests adapted to capability-hat behavioral assertions. forge build/fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (review)

Principal-engineer review of the merge surfaced an event-shape inconsistency:
setConfig(ROLE_PERM) emits single-flag RolePermSet(hat, flag), but bootstrapGlobalPerms
emitted a single combined-mask RolePermSet(hat, CREATE|REVIEW|...). The same event with
two shapes is a foot-gun for the subgraph indexer.

Now bootstrapGlobalPerms emits one single-flag RolePermSet per set bit, identical in shape
to the runtime path — one event == one gate→hat assignment. On-chain state is unchanged; no
test asserted the combined-mask event. A zero mask is a no-op (emits nothing).

Co-Authored-By: Claude Opus 4.8 (1M context) <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.

1 participant