13 KiB
13 KiB
Tasks: enterprise-permissions
Change ID: enterprise-permissions
Package: @suaveplan/permissions
Target path: universal/auth/permissions
Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/logger
1. Scaffold
- Create directory
universal/auth/permissions/withsrc/,tests/,src/__benchmarks__/, and all feature subdirectories per the layout inproposal.md. - Write
package.jsonat version0.1.0with:"type": "module","sideEffects": falseexportsmap with top-level"."plus subpaths:"./adapters/adapter-interface/adapter-interface","./adapters/memory-adapter/memory-adapter","./cache/permission-cache/permission-cache","./conditions/condition-matcher/condition-matcher","./conditions/field-matcher/field-matcher","./conditions/subject-detector/subject-detector","./core/ability/ability","./core/ability-builder/ability-builder","./core/permission-manager/permission-manager","./core/policy-engine/policy-engine","./core/role-manager/role-manager","./core/rule/rule","./middleware/permission-middleware/permission-middleware","./presets/role-presets/role-presets","./schemas/permissions.schemas","./types/permissions.types","./events/permission-events"dependencies:@suaveplan/error workspace:^,@suaveplan/types workspace:^,@suaveplan/events workspace:^,@suaveplan/logger workspace:^,zod catalog:forms,@opentelemetry/api catalog:observability,tslib catalog:builddevDependencies:vitest catalog:testing,@vitest/coverage-istanbul catalog:testing,fast-check catalog:testing,mitata catalog:testing,@suaveplan/testing workspace:^
- Write
tsconfig.jsonextending@suaveplan/typescript-config/library.json. - Write
vitest.config.tsusingcreateTestConfig()from@suaveplan/testing/config(pool:forks). - Add
lint,typecheck,test,test:coverage,build,docs:check,benchscripts topackage.json.
2. Types and errors
2a. Types (src/types/permissions.types.ts + .md)
- Define all public interfaces and type aliases from
proposal.md:PermissionAction,ConditionOperator,Permission,PermissionCondition,Role,UserRole,PermissionFilter,RoleFilter,PolicyRequest,AbilityRule,PermissionManagerOptions,RoleManagerOptions,PolicyEngineOptions,RoleAssignOptions,MiddlewareFn. - Write
permissions.types.md(≥200 words, API reference for every type).
2b. Error codes (src/error-codes.ts)
- Define
PermissionsErrorCodeunion — seven codes:PERM_ROLE_NOT_FOUND,PERM_PERMISSION_NOT_FOUND,PERM_ASSIGNMENT_NOT_FOUND,PERM_CIRCULAR_INHERITANCE,PERM_SYSTEM_ROLE_MUTATION,PERM_ADAPTER_ERROR,PERM_CACHE_ERROR. - Define
PERMISSIONS_ERROR_METADATAwithhttpStatus,severity,retryable,userMessageKey,category,descriptionper code. - Export
registerPermissionsErrorCodes()as idempotent eager side-effect. - Export
PermissionsError extends SuaveplanError<PermissionsErrorCode>.
2c. Zod schemas (src/schemas/permissions.schemas.ts + .md)
- Write Zod schemas:
PermissionSchema,RoleSchema,UserRoleSchema,PermissionManagerOptionsSchema,RoleManagerOptionsSchema,PolicyEngineOptionsSchema,PermissionCacheOptionsSchema. - Write
permissions.schemas.md. - Write
permissions.schemas.test.ts— parse valid/invalid for each schema.
3. Adapter interface
- Write
src/adapters/adapter-interface/adapter-interface.ts— fullGenericPermissionAdapterinterface fromproposal.md. - Write
adapter-interface.types.tswithStoredPermission,StoredRole,StoredPolicy,StoredPolicyCondition,StoredUserRole. - Write
adapter-interface.md.
4. MemoryAdapter
- Implement
MemoryAdapterusingMap-backed storage for permissions, roles, and user-role assignments. - Support optional
clock?: Clockfor expiry checks onUserRole.expiresAt. - Filter expired user-roles on lookup (lazy removal — no
setInterval). - Write
memory-adapter.test.ts— CRUD for permissions/roles/assignments, expiry with FakeClock, list filters, edge cases (not found). Eachithas{ timeout: 5000 }. - Write
memory-adapter.md.
5. Conditions system
5a. ConditionMatcher
- Implement
DefaultConditionMatcherandcompareValuessupporting all 11 operators fromConditionOperator. - Write
condition-matcher.test.ts— each operator for string/number/array/null inputs, boundary values. Eachithas{ timeout: 5000 }. - Write
condition-matcher.md.
5b. FieldMatcher
- Implement
DefaultFieldMatcherwith dot-notation path traversal andfieldMatcheshelper. - Write
field-matcher.test.ts— nested paths, missing fields, array index access. Eachithas{ timeout: 5000 }. - Write
field-matcher.md.
5c. SubjectDetector
- Implement
DefaultSubjectDetector— infers resource type from__typeproperty, constructor name, or explicit override. - Write
subject-detector.test.ts. - Write
subject-detector.md.
6. Core — Rule
- Implement
Rulewithmatches(resource, action)(exact + wildcard*) andevaluateConditions(context). - Write
rule.test.ts— wildcard action, wildcard resource, condition evaluation, negative conditions. Eachithas{ timeout: 5000 }. - Write
rule.md.
7. Core — Ability and AbilityBuilder
- Implement
Abilitywithcan,cannot,rulesFor. Wildcard*on action or resource grants all. - Implement
AbilityBuilderwith fluentcan/cannotmethods;build()returns frozenAbility. - Implement
buildAbility(fn)convenience factory. - Validate that
cannotrules overridecanrules (deny-overrides semantics). - Write
ability.test.ts— basic can/cannot, wildcard, conditions, rule precedence. Eachithas{ timeout: 5000 }. - Write
ability-builder.test.ts— fluent builder, complex ability, override ordering. - Write
ability.md,ability-builder.md.
8. Core — PermissionManager
- Implement
PermissionManager(adapter, options?):hasPermission(userId, resource, action, context?)— loads user roles, expands inheritance, evaluates conditions viaDefaultConditionMatcher; consultsPermissionCacheif wired.getUserPermissions(userId, siteId?)— resolves full effective permission set including inherited roles.- Emit
permission:checkevent onbusafter evaluation. - Instrument with
permissions.checkspan and histogram. - Accept optional
cache?: PermissionCache,bus?: EventBusLike,logger?: LoggerLike,clock?: Clock.
- Write
permission-manager.test.ts(≤500 lines) — CRUD ops,hasPermissionyes/no/conditions/inheritance, cache hit/miss, event emission, logger calls.{ timeout: 5000 }/{ timeout: 30000 }as appropriate. - Write
permission-manager.md.
9. Core — RoleManager
- Implement
RoleManager(adapter, options?):getRoleInheritanceChain(roleId)— DFS traversal; throwsPermissionsErrorwithPERM_CIRCULAR_INHERITANCEon cycle detection.assignRole/revokeRoleemitspermission:grant/permission:revokeevents onbus.- Guard
updateRole/deleteRoleagainstisSystem === trueroles; throwPERM_SYSTEM_ROLE_MUTATION. - Accept optional
bus?: EventBusLike,logger?: LoggerLike,clock?: Clock.
- Write
role-manager.test.ts— create/update/delete roles, inheritance chain, cycle detection, system-role guard, assignment/revocation, event emission. Eachithas{ timeout: 5000 }. - Write
role-manager.md.
10. Core — PolicyEngine
- Implement
PolicyEngine(permissionManager, options?):evaluate(userId, resource, action, context?)— delegates toPermissionManager.hasPermission; returnsPolicyResultwithmatchedRule,reason,conditions.evaluateMany(requests)— runs all in parallel withPromise.allSettled.- Instrument with
permissions.checkspan.
- Write
policy-engine.test.ts— single and bulk evaluation, denied/allowed paths, condition-driven. Eachithas{ timeout: 5000 }. - Write
policy-engine.md.
11. PermissionCache
- Implement
PermissionCache(options):- LRU eviction: when
maxSizeis reached, evict the oldest entry. - TTL eviction: entries older than
options.ttlms returnundefinedonget(lazy check via injectableclock). invalidate(userId)removes all entries whose key starts withuserId:.invalidateAll()clears the entire cache.
- LRU eviction: when
- Write
permission-cache.test.ts— get/set, TTL expiry with FakeClock, LRU eviction, invalidate userId, invalidateAll, size getter. Eachithas{ timeout: 5000 }. - Write
permission-cache.md.
12. Middleware
- Implement
createPermissionMiddleware(options)— framework-agnostic; includecreateHonoAdapterbridge for Hono consumers. - Implement standalone
checkPermission(userId, resource, action, adapter)helper. - Write
permission-middleware.test.ts— allowed/denied paths, user extractor, resource extractor. Eachithas{ timeout: 5000 }. - Write
permission-middleware.md.
13. Presets
- Implement
createAdminRole,createViewerRole,createEditorRole,createOwnerRoleandCRUD_PERMISSIONSconstant. - Write
role-presets.test.ts— verify preset permissions match expected access pattern. Eachithas{ timeout: 5000 }. - Write
role-presets.md.
14. Events
- Define
PermissionEventMapinterface andpermissionEventssingleton (re-exportedEventBus<PermissionEventMap>). - Write
permission-events.md.
15. OTel instrumentation (cross-cutting review)
- Confirm all spans, counters, and histograms from
proposal.md §4are wired. - Add
tests/otel.test.ts— in-memory exporter; assertpermissions.checkhistogram records onevaluate, counters increment on assign/revoke, cache counters increment on hit/miss.
16. Property-based tests (tests/property.test.ts)
Use fast-check. All test timeouts { timeout: 30000 }.
- Ability consistency: for any action/resource pair,
cannot(a, r)iff!can(a, r). - Role inheritance transitivity: three-level chain;
getUserPermissionscontains all inherited permissions. - Cache invalidation completeness: after
invalidate(userId),get(userId + ':' + any_suffix)returnsundefined. - Condition evaluation determinism:
compareValuesreturns the same result for identical inputs across multiple calls and arbitrary ordering. - PolicyEngine bulk consistency:
evaluateMany(requests)returns same results as sequentialevaluatecalls.
17. Benchmarks (src/__benchmarks__/permissions.bench.ts)
Use mitata.
hasPermission_no_cache: 10 000 checks, no-cache path, MemoryAdapter with 20 permissions.hasPermission_cached: 10 000 checks, warm PermissionCache.getRoleInheritanceChain_deep5: 10 000 calls, 5-level deep inheritance tree.ability_can_50rules: 100 000Ability.cancalls on a 50-ruleAbility.- Document placeholder numbers in
README.mdunder## Benchmarks.
18. Docs
Per-file .md (all ≥200 words):
src/types/permissions.types.mdsrc/adapters/adapter-interface/adapter-interface.mdsrc/adapters/memory-adapter/memory-adapter.mdsrc/cache/permission-cache/permission-cache.mdsrc/conditions/condition-matcher/condition-matcher.mdsrc/conditions/field-matcher/field-matcher.mdsrc/conditions/subject-detector/subject-detector.mdsrc/core/ability/ability.mdsrc/core/ability-builder/ability-builder.mdsrc/core/permission-manager/permission-manager.mdsrc/core/policy-engine/policy-engine.mdsrc/core/role-manager/role-manager.mdsrc/core/rule/rule.mdsrc/middleware/permission-middleware/permission-middleware.mdsrc/presets/role-presets/role-presets.mdsrc/schemas/permissions.schemas.mdsrc/events/permission-events.md
Root README:
- Write
universal/auth/permissions/README.md: purpose, install, quick-start (AbilityBuilder + PolicyEngine), modules table, benchmarks placeholder.
19. Verify and publish
- Run
bunx turbo lint --filter=@suaveplan/permissions— zero errors. - Run
bunx turbo typecheck --filter=@suaveplan/permissions— zero errors. - Run
bunx turbo test --filter=@suaveplan/permissions— all tests pass. - Run
bunx turbo test:coverage --filter=@suaveplan/permissions— 100/100/100/100. - Run
bun run docs:check --filter=@suaveplan/permissions— zero errors. - Confirm no
TODO,FIXME,@deprecated, untypedany,ts-ignore, or.skipinsrc/ortests/. - Confirm
"sideEffects": falseinpackage.json. - Run
bun scripts/publish.ts --package @suaveplan/permissions. - Update
CLAUDE.mdpackage listing to include@suaveplan/permissions. - Archive: move
openspec/changes/enterprise-permissions/toopenspec/archive/2026-04-24-enterprise-permissions/and fold spec delta intoopenspec/specs/permissions/spec.md.