@pikku/skills 0.12.22 → 0.12.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/dist/skills.gen.js +1 -1
  3. package/package.json +1 -1
  4. package/skills/pikku-a11y/SKILL.md +59 -0
  5. package/skills/pikku-addon/SKILL.md +2 -2
  6. package/skills/pikku-agent/SKILL.md +67 -316
  7. package/skills/pikku-agent/references/agents.md +299 -0
  8. package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
  9. package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
  10. package/skills/pikku-architect/SKILL.md +265 -0
  11. package/skills/pikku-auth/SKILL.md +89 -0
  12. package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +126 -34
  13. package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
  14. package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
  15. package/skills/pikku-auth/references/permissions.md +261 -0
  16. package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +0 -20
  17. package/skills/pikku-build/SKILL.md +88 -0
  18. package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +76 -24
  19. package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
  20. package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +56 -1
  21. package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
  22. package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
  23. package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +4 -20
  24. package/skills/{pikku-build-app → pikku-build}/references/ship.md +9 -3
  25. package/skills/pikku-concepts/SKILL.md +72 -7
  26. package/skills/pikku-concepts/references/concept-mapping.md +8 -8
  27. package/skills/pikku-deploy/SKILL.md +158 -0
  28. package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
  29. package/skills/pikku-deploy/references/cloudflare.md +104 -0
  30. package/skills/pikku-deploy/references/express.md +92 -0
  31. package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
  32. package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
  33. package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
  34. package/skills/pikku-deploy/references/uws.md +72 -0
  35. package/skills/pikku-deploy/references/ws.md +75 -0
  36. package/skills/pikku-emails/SKILL.md +3 -2
  37. package/skills/pikku-fabric/SKILL.md +47 -20
  38. package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
  39. package/skills/pikku-i18n/SKILL.md +62 -207
  40. package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
  41. package/skills/pikku-i18n/references/messages.md +218 -0
  42. package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
  43. package/skills/pikku-knowledge/SKILL.md +15 -0
  44. package/skills/pikku-kysely/SKILL.md +13 -13
  45. package/skills/pikku-list-query/SKILL.md +163 -0
  46. package/skills/pikku-meta/SKILL.md +58 -130
  47. package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
  48. package/skills/pikku-meta/references/meta.md +114 -0
  49. package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
  50. package/skills/pikku-middleware/SKILL.md +5 -5
  51. package/skills/pikku-n8n-import/SKILL.md +0 -1
  52. package/skills/pikku-permissions/SKILL.md +75 -229
  53. package/skills/pikku-react/SKILL.md +50 -298
  54. package/skills/pikku-react/references/client.md +313 -0
  55. package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
  56. package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
  57. package/skills/pikku-realtime/SKILL.md +110 -251
  58. package/skills/pikku-scenario/SKILL.md +60 -45
  59. package/skills/pikku-scenario/references/persona-run.md +148 -0
  60. package/skills/pikku-seo/SKILL.md +133 -0
  61. package/skills/pikku-service-backends/SKILL.md +154 -0
  62. package/skills/pikku-service-backends/references/aws.md +106 -0
  63. package/skills/pikku-service-backends/references/backblaze.md +57 -0
  64. package/skills/pikku-service-backends/references/mongodb.md +90 -0
  65. package/skills/pikku-service-backends/references/redis.md +75 -0
  66. package/skills/pikku-service-backends/references/schema.md +63 -0
  67. package/skills/pikku-services/SKILL.md +68 -291
  68. package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
  69. package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
  70. package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
  71. package/skills/pikku-services/references/services.md +272 -0
  72. package/skills/pikku-software-archaeology/README.md +5 -1
  73. package/skills/pikku-software-archaeology/SKILL.md +16 -2
  74. package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
  75. package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
  76. package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
  77. package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
  78. package/skills/pikku-webhook/SKILL.md +224 -0
  79. package/skills/pikku-wiring/SKILL.md +180 -0
  80. package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
  81. package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
  82. package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
  83. package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +3 -39
  84. package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
  85. package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
  86. package/skills/pikku-wiring/references/realtime.md +265 -0
  87. package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
  88. package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
  89. package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
  90. package/skills/pikku-workflow/SKILL.md +39 -2
  91. package/skills/pikku-aws/SKILL.md +0 -161
  92. package/skills/pikku-backblaze/SKILL.md +0 -104
  93. package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
  94. package/skills/pikku-deploy-express/SKILL.md +0 -122
  95. package/skills/pikku-deploy-uws/SKILL.md +0 -144
  96. package/skills/pikku-mongodb/SKILL.md +0 -113
  97. package/skills/pikku-product-second-opinion/README.md +0 -43
  98. package/skills/pikku-redis/SKILL.md +0 -99
  99. package/skills/pikku-schema-ajv/SKILL.md +0 -83
  100. package/skills/pikku-schema-cfworker/SKILL.md +0 -82
  101. package/skills/pikku-ws/SKILL.md +0 -87
  102. /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
  103. /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
  104. /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
  105. /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
  106. /package/skills/{pikku-realtime/references/other-routes.md → pikku-wiring/references/realtime-other-routes.md} +0 -0
@@ -1,280 +1,126 @@
1
1
  ---
2
2
  name: pikku-permissions
3
3
  description: >-
4
- Use when adding authorization checks to Pikku functions — pikkuPermission, pikkuAuth, scopes and
5
- defineScope, per-function permissions, global permissions, or understanding the scope/OR/AND
6
- gating logic. TRIGGER when: user wants to restrict who can call a function, check resource
7
- ownership, add role-based or scope-based access, declares or grants scopes, hits
8
- MissingScopeError, or asks where permission checks belong. DO NOT TRIGGER when: user asks about
9
- middleware or request interception (use pikku-middleware), authentication strategies (use
10
- pikku-security), or session management.
4
+ Use when deciding WHO may call a function — resource ownership, role gates, admin-only actions, or any "only their own rows" rule. Covers the `permissions` field, `pikkuPermission`, `pikkuAuth`, scopes, and where ownership belongs versus where it does not.
5
+ TRIGGER when: writing or reviewing any function that touches a row a user owns, gating an action on a role, building the permissions half of a contract in build PHASE 2, or about to write an `if` in a function body that decides whether the caller is allowed.
6
+ DO NOT TRIGGER when: the question is how to sign someone in or seed a persona (that is pikku-auth), or how to shape a paginated list (that is pikku-list-query).
11
7
  installGroups: [core]
12
8
  ---
13
9
 
14
10
  # Pikku Permissions
15
11
 
16
- ## ⛔ FIRST: is the caller a machine with a token? ⛔
12
+ ## The rule
17
13
 
18
- **Then this is NOT a permissions problem.** Resolve the token in `addHTTPMiddleware('*')` middleware that calls `setSession`, make the function a `pikkuFunc`, and gate it with `scopes`. A `permissions` check that verifies a bearer token and returns `true` is authentication wearing an authorization hat — and it leaves the function sessionless, so every body still has to work out who called it. See the machine-auth section of `pikku-middleware`. The only exception is a bootstrap endpoint whose caller has no identity yet (a shared-secret registration, a login): that one is sessionless and declares its gate here.
14
+ **Authorization goes in the `permissions` field. Never in the `func` body.**
19
15
 
20
- ## The Rule
21
-
22
- **ALWAYS put authorization checks in the `permissions` field of `pikkuFunc` or `pikkuSessionlessFunc` — NEVER inside the `func` body.**
23
-
24
- This includes: org access checks, repo access checks, role checks, resource ownership, and any other authorization logic. The `permissions` field runs before `func` and is visible to the inspector, so the gate is declared rather than buried — which is what lets `pikku info permissions` and an audit see it at all. Alongside it sits `scopes` (see below) for grant-based gating; between them they are where Pikku enforces authorization. The one sanctioned exception is `permissionsInBody`, covered at the end.
16
+ `permissions` runs before `func`, and it is DECLARED — `pikku meta` and the auditor can
17
+ see it. An `if` in the body is the same check, invisible: nothing can tell you which
18
+ functions are gated or how, and the next person to add a caller gets no warning.
25
19
 
26
20
  ```typescript
27
- // CORRECT
21
+ // RIGHT
28
22
  export const deleteBook = pikkuFunc({
29
- func: async ({ db }, { bookId }) => {
30
- await db.deleteBook(bookId)
31
- },
32
- permissions: {
33
- owner: isBookOwner, // ← authorization here
23
+ permissions: { owner: isBookOwner },
24
+ func: async ({ kysely }, { bookId }) => {
25
+ await kysely.deleteFrom('book').where('bookId', '=', bookId).execute()
34
26
  },
35
27
  })
36
28
 
37
- // WRONG — permission check inside func body
29
+ // WRONG — the gate is buried in the body
38
30
  export const deleteBook = pikkuFunc({
39
- func: async ({ db }, { bookId }, { session }) => {
40
- if (!session) throw new UnauthorizedError() // ← never do this
41
- await db.deleteBook(bookId)
31
+ func: async ({ kysely }, { bookId }, { session }) => {
32
+ const book = await kysely.selectFrom('book')...executeTakeFirst()
33
+ if (book?.ownerId !== session.userId) throw new UnauthorizedError()
34
+ await kysely.deleteFrom('book').where('bookId', '=', bookId).execute()
42
35
  },
43
36
  })
44
37
  ```
45
38
 
46
- ## Agent Operating Procedure
39
+ ## `auth: true` IS NOT OWNERSHIP — this is the one people get wrong
47
40
 
48
- 1. Discover before editing. Run `pikku info permissions --verbose` and `pikku info functions --verbose` to understand what permissions are already defined and applied.
49
- 2. Define permission checkers in a `src/permissions.ts` or domain-specific `src/lib/*-permissions.ts` file.
50
- 3. Apply them via the `permissions` field on the function. For an app-wide baseline that every function must additionally satisfy, use `addGlobalPermission`.
51
- 4. Validate: run `pikku all --tsc` to confirm permission checker signatures are correct.
41
+ `auth: true` means "somebody is signed in". It does NOT mean "this row is theirs". A CRUD
42
+ function set to `auth: true` and nothing else lets ANY signed-in user delete ANY other
43
+ user's row by passing its id. Every function that takes a row id needs BOTH: `auth: true`
44
+ for the session, and a `permissions` entry for the ownership.
52
45
 
53
- ## Permission Factories
46
+ Equally: do NOT write an `isSignedIn` permission that returns `!!session`. That re-checks
47
+ authentication, which `auth: true` already did. A permission answers *may this user do
48
+ this* — role, ownership, tier — never *is there a session*.
54
49
 
55
- ### `pikkuAuth(fn)` — Session-Only Checks
50
+ ## Single row vs list — where ownership actually goes
56
51
 
57
- Use for checks that read the session but need no request data — and that assert
58
- something **beyond** merely having a session (a flag, a tier, a claim).
52
+ This is the distinction to get right, and both halves are correct code:
59
53
 
60
- ```typescript
61
- import { pikkuAuth } from '#pikku/auth'
54
+ - **A function taking a row id** (`get`, `update`, `delete`) — ownership is a
55
+ PERMISSION. Load the row, compare the owner to the session. It is a yes/no question
56
+ about one row, which is exactly what a permission is.
57
+ - **A function returning many rows** (`list`, `search`, any stats query) — ownership is
58
+ a `WHERE` clause in the query, because "only their rows" is a filter, not a yes/no.
59
+ There is no permission to write here; scoping the query IS the enforcement.
62
60
 
63
- // Good: a real gate on the session's contents, not just its existence.
64
- export const isVerified = pikkuAuth(
65
- async (_services, session) => !!session?.emailVerified
66
- )
67
- ```
68
-
69
- **Do NOT write an "is signed in" permission.** A checker that just returns
70
- `!!session` is not authorization — it re-checks authentication, which the
71
- function already enforces. A function that needs a signed-in user sets
72
- `auth: true` (the default for `pikkuFunc`); it does not also carry a
73
- `permissions: { signedIn }`.
61
+ A list that fetches everything and then filters in JS is a bug, not a permission.
74
62
 
75
- ```typescript
76
- // WRONG — redundant with auth: true; adds a permission that gates nothing.
77
- export const isSignedIn = pikkuAuth(async (_s, session) => !!session)
78
- pikkuFunc({ auth: true, permissions: { signedIn: isSignedIn } /* ... */ })
63
+ ## Writing the checkers
79
64
 
80
- // RIGHT — auth: true already requires the session; permissions are for capability.
81
- pikkuFunc({ auth: true /* ... */ })
82
- ```
83
-
84
- A permission answers "_may this user do this?_" (role, ownership, tier) — never
85
- "_is there a session?_".
86
-
87
- ### `pikkuPermission(fn)` — Data-Aware Checks
88
-
89
- Use when authorization depends on the actual request data (e.g., resource ownership).
65
+ Put them in `src/permissions/`, one file per entity, and reuse one checker across every function on that
66
+ entity rather than writing a near-copy per function.
90
67
 
91
68
  ```typescript
92
- import { pikkuPermission } from '#pikku/auth'
69
+ // src/permissions/book.ts
70
+ import { pikkuPermission, pikkuAuth } from '#pikku/auth'
93
71
 
72
+ // Data-aware: gets the input, so it can load the row the caller named.
94
73
  export const isBookOwner = pikkuPermission(
95
- async ({ db }, { bookId }, { session }) => {
96
- const book = await db.getBook(bookId)
97
- return book?.authorId === session?.userId
98
- }
74
+ async ({ kysely }, { bookId }, { session }) => {
75
+ const book = await kysely
76
+ .selectFrom('book')
77
+ .select('ownerId')
78
+ .where('bookId', '=', bookId)
79
+ .executeTakeFirst()
80
+ return book?.ownerId === session?.userId
81
+ },
99
82
  )
100
83
 
101
- export const hasBookAccess = pikkuPermission(
102
- async ({ db }, { bookId }, { session }) => {
103
- return await db.hasAccess(session?.userId, bookId)
104
- }
105
- )
84
+ // Session-only: no input needed. Use for role and flag gates.
85
+ export const isAdmin = pikkuAuth(async (_services, session) => session?.role === 'admin')
106
86
  ```
107
87
 
108
- ## OR / AND Logic
88
+ ## OR and AND
109
89
 
110
90
  ```typescript
111
91
  permissions: {
112
- verified: isVerified, // OR: verified users can access
113
- owner: isBookOwner, // OR: owners can access
114
- reviewer: [isVerified, hasBookAccess], // AND: both must pass
92
+ owner: isBookOwner, // OR — an owner may
93
+ admin: isAdmin, // OR — an admin may
94
+ editor: [isAdmin, isBookOwner] // AND — both, inside one group
115
95
  }
116
- // Logic: verified OR owner OR (isVerified AND hasBookAccess)
117
- ```
118
-
119
- Groups are OR'd. Entries within a group array are AND'd.
120
-
121
- ## Where to Apply Permissions
122
-
123
- ### Per-Function (preferred)
124
-
125
- ```typescript
126
- export const deleteBook = pikkuFunc({
127
- func: async ({ db }, { bookId }) => {
128
- await db.deleteBook(bookId)
129
- },
130
- permissions: {
131
- verified: isVerified,
132
- owner: isBookOwner,
133
- },
134
- })
135
- ```
136
-
137
- ### Global (`addGlobalPermission`) — App-Wide AND Gate
138
-
139
- A global permission is an app-wide baseline that **every** function must additionally pass. It is an independent AND gate: it can only ever _narrow_ access — it never grants access a function's own `permissions` would deny.
140
-
141
- ```typescript
142
- import { addGlobalPermission } from '#pikku/auth'
143
-
144
- addGlobalPermission([isEmployee]) // every function now also requires an employee session
145
- ```
146
-
147
- Multiple `addGlobalPermission` calls accumulate and are AND'd together.
148
-
149
- > Wire-, tag-, and HTTP-route-level permissions (`addHTTPPermission`, `addTagPermission`, and a `permissions` field on HTTP/channel/MCP wirings) were **removed in #972**. Permissions now live only on the function definition, plus the optional global gate. Tags are organizational only — use tag/HTTP _middleware_ (`addTagMiddleware`, `addHTTPMiddleware`) for cross-cutting request handling, not authorization.
150
-
151
- ## Scopes — the AND Gate Above Permissions
152
-
153
- Scopes answer "what was this session granted?" before permissions ask "may this
154
- user do this to this resource?". They are AND-ed: every scope listed must be
155
- held. Because they are checked first and fail closed, a scope can only ever
156
- _narrow_ access — it never grants what `permissions` would deny.
157
-
158
- Declare the scope tree once with `defineScope`. The body is a no-op that
159
- tree-shakes away; the CLI reads the call by AST and generates a `ScopeId` union,
160
- so a function naming an undeclared scope fails the build rather than silently
161
- gating on nothing.
162
-
163
- ```typescript
164
- // src/scopes.ts
165
- import { defineScope } from '#pikku/scopes'
166
-
167
- defineScope({
168
- admin: {
169
- displayName: 'Administration',
170
- description: 'Administrative access',
171
- scopes: {
172
- invoices: {
173
- description: 'Invoice management',
174
- scopes: {
175
- create: { description: 'Create invoices' },
176
- void: { description: 'Void invoices' },
177
- },
178
- },
179
- },
180
- },
181
- billing: {},
182
- })
183
96
  ```
184
97
 
185
- Every node is grantable, keyed by segment: the above yields `admin`,
186
- `admin:invoices`, `admin:invoices:create`, `admin:invoices:void` and `billing`.
187
- Scopes may be declared across more than one file — the declarations merge.
98
+ Groups are OR'd; entries inside a group array are AND'd.
188
99
 
189
- ```typescript
190
- export const voidInvoice = pikkuFunc({
191
- scopes: ['admin:invoices:void'],
192
- permissions: { owner: isInvoiceOwner },
193
- func: async ({ db }, { invoiceId }) => { ... },
194
- })
195
- ```
196
-
197
- A grant satisfies a required scope if it is the scope itself, an ancestor of it,
198
- or a wildcard at any level — so a session holding `admin` satisfies
199
- `admin:invoices:void`, and `admin:*` does too. A missing scope throws
200
- `MissingScopeError` naming the first one that failed.
201
-
202
- `scopes` requires a session and so is unavailable on `pikkuSessionlessFunc`:
203
- scopes fail closed, an anonymous caller holds none, and a sessionless function
204
- with scopes would reject every caller it exists to serve. Gate those with
205
- `permissions`, which receive the optional session and may pass anonymous.
100
+ ## Roles
206
101
 
207
- ## The Three Gates
102
+ If the app has roles, the role lives on the session (see pikku-auth / `mapSession`) and
103
+ every mutating or admin-only function names it in `permissions`. Gate the FUNCTION — hiding
104
+ an admin button in the UI is UX, never enforcement, and a member who guesses the RPC name
105
+ gets straight through if the function itself is open.
208
106
 
209
- Authorization is three independent gates, evaluated in this order, all of which must pass:
107
+ ## Scopes
210
108
 
211
- 1. **Scopes** (`scopes`) — AND'd, checked before input validation. Fails closed.
212
- 2. **Global permissions** (`addGlobalPermission`) — AND'd together. A broad baseline that can only narrow access.
213
- 3. **The function's own `permissions`** — OR'd groups (OR-of-ANDs), as above.
109
+ `scopes: ['admin:invoices:void']` is an AND gate checked BEFORE permissions and before
110
+ input validation. Declare the tree once with `defineScope`; a function naming an
111
+ undeclared scope fails codegen rather than gating on nothing. A grant satisfies a scope if
112
+ it is that scope, an ancestor, or a wildcard — a session holding `admin` satisfies
113
+ `admin:invoices:void`. Most apps need roles, not scopes; reach for these only when the
114
+ plan asked for granular grants.
214
115
 
215
- The gates are independent: a broad global (e.g. `isEmployee`) can **never** satisfy an admin-only function's own requirement. Each function still enforces its own `scopes` and `permissions` in full.
116
+ ## The one sanctioned exception
216
117
 
217
- ## The Sanctioned Exception: `permissionsInBody`
118
+ `permissionsInBody: true` — for a check that genuinely cannot be a permission because the
119
+ identity arrives in the payload and there is no session: a webhook signature, a signed
120
+ token, an invite code. It is purely declarative and enforces nothing; its job is to tell
121
+ the auditor the openness is deliberate. Anything expressible as a permission must be one.
218
122
 
219
- A few checks genuinely cannot be expressed as a permission — verifying a webhook
220
- signature, a signed token, or an invite code, where the "identity" arrives in the
221
- payload and there is no session to check. For those, declare
222
- `permissionsInBody: true` on the function and keep the check in the body.
123
+ ## After changes
223
124
 
224
- ```typescript
225
- export const handleStripeWebhook = pikkuSessionlessFunc({
226
- permissionsInBody: true,
227
- auth: false,
228
- func: async ({ stripe }, data, { http }) => {
229
- stripe.webhooks.constructEvent(
230
- data.raw,
231
- http.request.header('stripe-signature'),
232
- secret
233
- )
234
- // ...
235
- },
236
- })
237
- ```
238
-
239
- This is a last resort, and it is purely declarative — it grants nothing and
240
- enforces nothing. Its only job is to tell the auditor that this function's
241
- apparent openness is deliberate, so asserting it falsely disables the very check
242
- that would have caught the mistake. It requires `"allow": { "permissionsInBody": true }`
243
- in `pikku.config.json`, which keeps the decision visible at the project level.
244
- Prefer `permissions` whenever the check can be expressed as one — they are
245
- declared, inspectable, and reusable.
246
-
247
- ## Complete Example
248
-
249
- ```typescript
250
- // src/permissions.ts
251
- import { pikkuAuth, pikkuPermission } from '#pikku/auth'
252
-
253
- export const isVerified = pikkuAuth(
254
- async (_services, session) => !!session?.emailVerified
255
- )
256
-
257
- export const isOrgMember = pikkuPermission(
258
- async ({ db }, { orgId }, { session }) => {
259
- return await db.isMember(session?.userId, orgId)
260
- }
261
- )
262
-
263
- // src/functions/org.function.ts
264
- export const deleteOrg = pikkuFunc({
265
- func: async ({ db }, { orgId }) => {
266
- await db.deleteOrg(orgId)
267
- },
268
- permissions: {
269
- verified: isVerified,
270
- owner: [isVerified, isOrgMember],
271
- },
272
- })
273
- ```
274
-
275
- ## After Changes
276
-
277
- ```bash
278
- pikku all # regenerate if wirings changed
279
- pikku all --tsc # regenerate, then verify permission checker types (fails on type errors)
280
- ```
125
+ `pikku all` — regenerates and typechecks the checkers. A permission whose signature is
126
+ wrong fails here, not at runtime.