You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(policy): decentralize subject resolution to modules (#3382)
* refactor(policy): decentralize subject resolution to modules
Replace hardcoded resolveSubject() and deriveSubjectType() in
lib/middlewares/policy.js with a registry pattern. Each module policy
file now exports a *SubjectRegistration() function that registers its
own document-level and path-level CASL subjects during discoverPolicies().
Adds lib/helpers/authorize.js as a simple route-level middleware helper.
Closes#3381
* docs(skills): enforce decentralized policy pattern in skills
* docs(skills): enforce module autonomy and decentralization in skills
* fix(policy): address CodeRabbit review — idempotency, path precision, docs
* fix(policy): complete JSDoc returns, add deprecation window, clarify feature skill boundaries
* fix(skills): allow justified lib/services/ changes alongside lib/helpers/ in feature boundaries
* fix(policy): use response helper in authorize, complete JSDoc returns on all policy registrations
- If the module needs authorization: create `policies/{module}.policy.js` with ability builder + subject registration exports
73
+
74
+
### 6. Apply renames carefully
68
75
69
76
- Case-sensitive, whole-word matches where possible
70
77
- Show plan before applying if many files affected
71
78
- Don't rename unrelated code (e.g., "tasks" in comments about other features)
72
79
73
-
### 6. Verify & report
80
+
### 7. Verify & report
74
81
75
82
Run `/verify`, then report: module path, renamed tokens, lint/test results, next steps (customize schema/services — routes auto-discovered via `modules/*/routes/*.js`).
Copy file name to clipboardExpand all lines: .claude/skills/feature/SKILL.md
+21-10Lines changed: 21 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,13 @@ description: Implement a new feature or modify existing functionality. Use when
12
12
- Which module? Default to **ONE** unless justified.
13
13
-**If the module doesn't exist** → run `/create-module` to scaffold it first, then continue.
14
14
15
-
### 2. Analyze flows & edge cases
15
+
### 2. Module boundaries
16
+
17
+
- All new code isolated inside the target module (`modules/{module}/`)
18
+
- No modifications to shared core files (`lib/middlewares/`, `config/`) — except `config/templates/` for new email templates; additions to `lib/helpers/` or `lib/services/` require explicit justification
19
+
- Module registers its own capabilities via exports (policies, subjects) and file discovery (config auto-discovered by filepath pattern) — auto-discovered by the core
20
+
21
+
### 3. Analyze flows & edge cases
16
22
17
23
For each user-facing flow this feature creates or modifies, identify:
18
24
@@ -22,13 +28,13 @@ For each user-facing flow this feature creates or modifies, identify:
22
28
-**Retry edge** — can the user retry after failure/rejection? (check unique indexes)
23
29
-**Multi-user impact** — who else is affected? Do they need notification?
24
30
25
-
### 3. Check boilerplate resilience
31
+
### 4. Check boilerplate resilience
26
32
27
33
- Works WITHOUT mailer configured? (graceful skip, no crash)
28
34
- Works WITHOUT organizations enabled?
29
35
- No hard dependency on external services for core flow?
30
36
31
-
### 4. Present plan & ask questions
37
+
### 5. Present plan & ask questions
32
38
33
39
**STOP and present to the user:**
34
40
- Flows identified (happy + error + edge cases)
@@ -39,7 +45,7 @@ For each user-facing flow this feature creates or modifies, identify:
-**Services**: Business logic, call repositories, throw `AppError`
52
58
-**Repositories**: Database only — sole layer importing mongoose
53
59
54
-
### 6. Apply modularity rules
60
+
### 7. Apply modularity rules
55
61
56
62
- Isolate inside module boundary
57
63
- No cross-module imports unless justified (shared code → `lib/helpers/`)
58
64
-**No cross-module Repository/Model imports** — a service must never import another module's repositories or models; use the target module's Service instead
59
65
- Follow `/naming` conventions
60
66
61
-
### 7. Handle notifications
67
+
### 8. Handle notifications
62
68
63
69
If an action affects another user:
64
70
- Use `lib/helpers/mailer/` abstraction (never nodemailer directly)
@@ -68,7 +74,7 @@ If an action affects another user:
@@ -83,6 +89,11 @@ If an action affects another user:
83
89
-[ ] New enum values added to ALL schema definitions (Mongoose model `enum`, Zod `z.enum`, tests)
84
90
-[ ] Grep existing enum values to find all locations before committing
85
91
92
+
**Module autonomy:**
93
+
-[ ] No shared-file changes unless explicitly required; any `lib/helpers/` or `lib/services/` additions include explicit justification, and `config/templates/` is used for new email templates
94
+
-[ ] Module self-registers capabilities (subjects, abilities) via policy exports
95
+
-[ ] New routes/middleware defined inside the module boundary
96
+
86
97
**Modularity:**
87
98
-[ ] Isolated in ONE module (or justified)
88
99
-[ ] No cross-module Repository/Model imports (use target module's Service)
@@ -95,10 +106,10 @@ If an action affects another user:
95
106
**Error documentation:**
96
107
-[ ] If a non-obvious bug was fixed, add a single-line entry to `ERRORS.md` (root of repo) using format: `[YYYY-MM-DD] <scope>: <wrong> -> <right>` (see existing examples in `ERRORS.md`)
97
108
98
-
### 8b. Elegance check
109
+
### 9b. Elegance check
99
110
100
111
For non-trivial changes: pause and ask yourself "is there a simpler or more elegant approach?" If the current implementation feels hacky, refactor before proceeding.
Subject resolution in `lib/middlewares/policy.js` is now registry-based instead of hardcoded. Each module's policy file exports a `*SubjectRegistration()` function that registers its own document-level and path-level subjects during `discoverPolicies()`.
10
+
11
+
### What changed
12
+
13
+
-`resolveSubject()` iterates `documentSubjectRegistry` instead of hardcoded if/else chain
14
+
-`deriveSubjectType()` iterates `pathSubjectRegistry` instead of hardcoded if/else chain
15
+
- New exports: `registerDocumentSubject`, `registerPathSubject`
16
+
- New helper: `lib/helpers/authorize.js` — simple middleware for route-level CASL checks
17
+
- Each module policy file now exports a `*SubjectRegistration({ registerDocumentSubject, registerPathSubject })` function
18
+
19
+
### Action for downstream
20
+
21
+
1. Run `/update-stack` to pull the change
22
+
2. If you have custom modules with policy files, add a `*SubjectRegistration()` export following the pattern in any existing module (e.g. `modules/tasks/policies/tasks.policy.js`)
23
+
3.`policy.isAllowed` continues to work unchanged — no route file modifications needed
24
+
4. Optional: use `authorize(action, subject)` from `lib/helpers/authorize.js` for simple route guards
25
+
26
+
> **Deprecation notice**: `policy.isAllowed` is supported for this release cycle only. New routes should use `authorize(action, subject)` from `lib/helpers/authorize.js`. Custom modules using `policy.isAllowed` should migrate to `authorize()` before the next major version. The legacy middleware will be removed once all built-in module routes have been migrated.
27
+
28
+
---
29
+
7
30
## Audit GDPR Flags (2026-03-26)
8
31
9
32
New config flags to control IP and User-Agent capture in audit logs for GDPR compliance.
0 commit comments