@pikku/skills 0.12.54 → 0.12.56
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.
- package/README.md +1 -1
- package/dist/skills.gen.js +2 -2
- package/dist/snippets.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-architect/SKILL.md +1 -1
- package/skills/pikku-architect/references/plan-defects.md +7 -7
- package/skills/pikku-auth/references/better-auth.md +92 -96
- package/skills/pikku-build/SKILL.md +9 -9
- package/skills/pikku-build/references/app.md +56 -61
- package/skills/pikku-build/references/design.md +14 -14
- package/skills/pikku-build/references/feature.md +3 -3
- package/skills/pikku-build/references/mocks.md +6 -0
- package/skills/pikku-build/references/multi-app.md +16 -16
- package/skills/pikku-build/references/openapi.md +34 -34
- package/skills/pikku-build/references/platform.md +4 -5
- package/skills/pikku-build/references/post-clone.md +1 -1
- package/skills/pikku-build/references/quick.md +63 -36
- package/skills/pikku-build/references/scenarios.md +17 -16
- package/skills/pikku-build/references/ship.md +14 -16
- package/skills/pikku-build/references/theming.md +2 -2
- package/skills/pikku-changes/SKILL.md +21 -5
- package/skills/pikku-concepts/SKILL.md +26 -27
- package/skills/pikku-concepts/references/language.md +6 -6
- package/skills/pikku-deploy/references/nextjs.md +1 -1
- package/skills/pikku-guide/SKILL.md +18 -17
- package/skills/pikku-i18n/references/messages.md +5 -6
- package/skills/pikku-knowledge/SKILL.md +3 -3
- package/skills/pikku-meta/references/audit.md +1 -1
- package/skills/pikku-permissions/SKILL.md +1 -1
- package/skills/pikku-react/SKILL.md +18 -20
- package/skills/pikku-react/references/client.md +2 -37
- package/skills/pikku-report/SKILL.md +10 -10
- package/skills/pikku-scenario/SKILL.md +63 -66
- package/skills/pikku-scenario/references/browser.md +5 -5
- package/skills/pikku-scenario/references/coverage.md +4 -4
- package/skills/pikku-scenario/references/steps.md +31 -30
- package/skills/pikku-scenario/references/{persona-run.md → virtual-user-run.md} +38 -43
- package/skills/pikku-scenario/references/virtual-users.md +114 -0
- package/skills/pikku-seo/SKILL.md +1 -1
- package/skills/pikku-services/references/audit-wire-service.md +2 -2
- package/skills/pikku-services/references/audit.md +1 -2
- package/skills/pikku-services/references/services.md +1 -1
- package/skills/pikku-software-archaeology/SKILL.md +13 -13
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +9 -9
- package/skills/pikku-theme/SKILL.md +1 -1
- package/skills/pikku-workflow/SKILL.md +1 -1
- package/skills/pikku-admin-to-fabric/SKILL.md +0 -198
- package/skills/pikku-blueprint-to-fabric/SKILL.md +0 -362
- package/skills/pikku-blueprint-to-fabric/scripts/inventory.mjs +0 -367
- package/skills/pikku-fabric/SKILL.md +0 -331
- package/skills/pikku-fabric/references/database.md +0 -128
- package/skills/pikku-fabric/references/debugging.md +0 -114
- package/skills/pikku-fabric/references/deploy.md +0 -179
- package/skills/pikku-scenario/references/personas.md +0 -123
|
@@ -14,13 +14,13 @@ the real API. This reference is the decision that shapes the app around it:
|
|
|
14
14
|
|
|
15
15
|
## Pick the auth mode
|
|
16
16
|
|
|
17
|
-
| The upstream…
|
|
18
|
-
|
|
|
19
|
-
| Is where the users already have accounts, and has a login endpoint
|
|
20
|
-
| Takes a per-user API key, bearer token or basic auth
|
|
21
|
-
| Uses OAuth2
|
|
22
|
-
| Is used on everyone's behalf with one key the business owns
|
|
23
|
-
| Takes no auth
|
|
17
|
+
| The upstream… | Mode | Command | Who signs in, and how |
|
|
18
|
+
| ------------------------------------------------------------------ | -------------- | ---------------------- | ------------------------------------------------------- |
|
|
19
|
+
| Is where the users already have accounts, and has a login endpoint | Delegated | `--auth-config <file>` | Users sign in with their upstream login; no new account |
|
|
20
|
+
| Takes a per-user API key, bearer token or basic auth | Per-user key | (the default) | App account, then a Connect screen for the key |
|
|
21
|
+
| Uses OAuth2 | Per-user OAuth | (the default) | App account, then Connect via the OAuth consent |
|
|
22
|
+
| Is used on everyone's behalf with one key the business owns | Shared secret | `--auth shared` | App account only; the key is a secret |
|
|
23
|
+
| Takes no auth | None | `--auth none` | App account only |
|
|
24
24
|
|
|
25
25
|
Per-user is the default because the upstream then enforces each person's own
|
|
26
26
|
permissions. A shared secret acts with one identity for everyone, so the
|
|
@@ -69,30 +69,30 @@ JSON, passed with `--auth-config`. Every field is optional except where noted.
|
|
|
69
69
|
|
|
70
70
|
Top level — how every call authenticates:
|
|
71
71
|
|
|
72
|
-
| Field | Meaning
|
|
73
|
-
| -------------- |
|
|
74
|
-
| `headerName` | The header the API reads the token or key from. Setting it alone means a per-user API key
|
|
72
|
+
| Field | Meaning |
|
|
73
|
+
| -------------- | ------------------------------------------------------------------------------------------------------ |
|
|
74
|
+
| `headerName` | The header the API reads the token or key from. Setting it alone means a per-user API key |
|
|
75
75
|
| `headerFormat` | `raw` sends the bare value, `bearer` prefixes `Bearer `. Default: bearer for `Authorization`, else raw |
|
|
76
|
-
| `extraHeaders` | Static headers on every request, login included — for upstreams that route on a header
|
|
77
|
-
| `delegated` | Present when users sign in with their upstream login
|
|
76
|
+
| `extraHeaders` | Static headers on every request, login included — for upstreams that route on a header |
|
|
77
|
+
| `delegated` | Present when users sign in with their upstream login |
|
|
78
78
|
|
|
79
79
|
`delegated`:
|
|
80
80
|
|
|
81
|
-
| Field
|
|
82
|
-
|
|
|
83
|
-
| `loginPath`
|
|
84
|
-
| `loginMethod`
|
|
85
|
-
| `credentials`
|
|
86
|
-
| `fields`
|
|
87
|
-
| `encoding`
|
|
88
|
-
| `apiKeyHeader`
|
|
89
|
-
| `tokenPath`
|
|
90
|
-
| `expiresAtPath`
|
|
91
|
-
| `identity`
|
|
92
|
-
| `claims.source`
|
|
93
|
-
| `claims.*`
|
|
94
|
-
| `emailTemplate`
|
|
95
|
-
| `roles`
|
|
81
|
+
| Field | Meaning |
|
|
82
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
83
|
+
| `loginPath` | Required. The spec-relative login operation |
|
|
84
|
+
| `loginMethod` | Default `post` |
|
|
85
|
+
| `credentials` | What the sign-in form collects: `login` (a username or email), `email`, `password`, `apiKey`. Default `["email","password"]` |
|
|
86
|
+
| `fields` | The upstream's name for each credential, e.g. `{ "login": "username" }` |
|
|
87
|
+
| `encoding` | `json`, `form` or `query` — how the login fields travel. Default `json` |
|
|
88
|
+
| `apiKeyHeader` | The header an `apiKey` credential is sent in. Default `x-api-key` |
|
|
89
|
+
| `tokenPath` | Required. Dot-path to the token in the login response |
|
|
90
|
+
| `expiresAtPath` | Dot-path to an epoch-seconds expiry. Defaults to the JWT's `exp` when claims come from the JWT |
|
|
91
|
+
| `identity` | An operation that returns the signed-in user, called with the new token — for logins that return only a token |
|
|
92
|
+
| `claims.source` | `jwt`, `response` or `identity`. Default `identity` when `identity` is set, else `response` |
|
|
93
|
+
| `claims.*` | Dot-paths to `externalId`, `email`, `name` (one path or several joined with a space), `role`, `tenantId` |
|
|
94
|
+
| `emailTemplate` | Builds an email for an upstream user who has none: `{login}`, `{externalId}`, `{host}`. Such an address never claims an existing user |
|
|
95
|
+
| `roles` | Maps the raw `claims.role` value onto an app role; an unmapped value gets no role |
|
|
96
96
|
|
|
97
97
|
Check it before building on it: start `pikku dev`, sign in once with
|
|
98
98
|
`POST /api/auth/sign-in/delegated` as a real upstream user, and call an exposed
|
|
@@ -100,7 +100,7 @@ operation with the session. A wrong password must be refused. Keep real
|
|
|
100
100
|
credentials in the environment, never in the config or a test.
|
|
101
101
|
|
|
102
102
|
What the install wires for delegated mode — `pikkuDelegatedAuth` in
|
|
103
|
-
`src/auth.ts`, the token stored per user,
|
|
103
|
+
`src/auth.ts`, the token stored per user, virtual users carrying it into scenarios — is
|
|
104
104
|
in the `pikku-auth` skill's `references/better-auth.md`.
|
|
105
105
|
|
|
106
106
|
## The screens
|
|
@@ -114,13 +114,13 @@ in the `pikku-auth` skill's `references/better-auth.md`.
|
|
|
114
114
|
call fails with `missing_credential`.
|
|
115
115
|
- A `credential_rejected` error (`CredentialRejectedError`, 403) means the
|
|
116
116
|
upstream refused the stored token: show the sign-in again (`reauth:
|
|
117
|
-
|
|
117
|
+
'sign-in'`) or the Connect screen (`reauth: 'connect'`), not a generic error.
|
|
118
118
|
|
|
119
119
|
## Scenarios
|
|
120
120
|
|
|
121
|
-
A
|
|
122
|
-
act with. The install adds `credentials` to `
|
|
123
|
-
at sign-in each
|
|
124
|
-
environment (e.g. `
|
|
121
|
+
A virtual user that signs in through the upstream needs an upstream credential to
|
|
122
|
+
act with. The install adds `credentials` to `pikkuVirtualUser` in `src/auth.ts`, and
|
|
123
|
+
at sign-in each virtual user stores `VIRTUAL_USER_CREDENTIAL_<VIRTUAL_USER>_<NAME>` from the
|
|
124
|
+
environment (e.g. `VIRTUAL_USER_CREDENTIAL_SALES_REP_DOLIBARR`). Without it every
|
|
125
125
|
scenario step that reaches the upstream fails with `missing_credential`. The
|
|
126
|
-
`pikku-scenario` skill's `references/
|
|
126
|
+
`pikku-scenario` skill's `references/virtual-users.md` has the details.
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
- Reference
|
|
12
12
|
|
|
13
13
|
**This file is a delta: it adds to the app workflow, which stays the base and is
|
|
14
|
-
followed in full.** Everything there applies: knowledge base first,
|
|
14
|
+
followed in full.** Everything there applies: knowledge base first, virtual users and roles,
|
|
15
15
|
changes grouped into changesets, planned then built one at a time, scenarios, design pass, deploy
|
|
16
|
-
gates,
|
|
16
|
+
gates, deploy-readiness. This file adds the surfaces that turn an app into a
|
|
17
17
|
demonstration of the platform, and says where each one slots into that workflow.
|
|
18
18
|
|
|
19
19
|
Do not blend the two into one plan —
|
|
@@ -86,7 +86,7 @@ on a human or a timer and must survive a restart.
|
|
|
86
86
|
gap in the middle? If every operation completes in one request, you do not need
|
|
87
87
|
workflows and forcing one is noise.
|
|
88
88
|
- **Prove it:** a scenario that starts the workflow, advances it as a second
|
|
89
|
-
|
|
89
|
+
virtual user, and asserts the end state. `pikku-react` covers driving it
|
|
90
90
|
from the UI.
|
|
91
91
|
- Three workflows ship with the template. Read them before writing yours.
|
|
92
92
|
|
|
@@ -133,7 +133,7 @@ generated typed client.
|
|
|
133
133
|
- **Motivation test:** two people looking at the same thing at the same time, or
|
|
134
134
|
a long operation whose progress matters.
|
|
135
135
|
- **Prove it:** a browser scenario is the only honest proof — assert the second
|
|
136
|
-
|
|
136
|
+
virtual user's screen changed without a reload.
|
|
137
137
|
|
|
138
138
|
### MCP — `pikku-wiring`
|
|
139
139
|
|
|
@@ -241,4 +241,3 @@ built.
|
|
|
241
241
|
- Per-surface skills: `pikku-workflow`, `pikku-wiring`, `pikku-agent`,
|
|
242
242
|
`pikku-i18n`, `pikku-emails`,
|
|
243
243
|
`pikku-meta`, `pikku-addon`, `pikku-auth`, `pikku-services`
|
|
244
|
-
- Every feature, end to end: https://pikkufabric.com/llm-all-features.txt
|
|
@@ -35,5 +35,5 @@ part of cleanup — this step is project hygiene, not a build.
|
|
|
35
35
|
Templates are structure-only starting points. Without this pass, clones carry a
|
|
36
36
|
misleading README, a placeholder package name, and (historically) a gitignored
|
|
37
37
|
lockfile — all of which leak template assumptions into a real project. Running
|
|
38
|
-
it immediately after clone keeps every Pikku project
|
|
38
|
+
it immediately after clone keeps every Pikku project starting
|
|
39
39
|
from a clean, honest baseline.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
- Agent Operating Procedure
|
|
6
6
|
- 1. One question, then build
|
|
7
|
-
- 2.
|
|
7
|
+
- 2. Virtual users — 60 seconds, not optional
|
|
8
8
|
- 3. Build
|
|
9
9
|
- 4. Look at it — actually
|
|
10
10
|
- 5. One smoke scenario
|
|
@@ -15,12 +15,12 @@ signed-in screens in as few steps as possible.
|
|
|
15
15
|
|
|
16
16
|
**What this mode deliberately skips**, and what that costs:
|
|
17
17
|
|
|
18
|
-
| Skipped
|
|
19
|
-
|
|
20
|
-
| `knowledge/`
|
|
21
|
-
| Changesets and plans | No build order, no per-piece proof. Fine at this size, painful past it.
|
|
22
|
-
| Design direction
|
|
23
|
-
| Refusal scenarios
|
|
18
|
+
| Skipped | Cost |
|
|
19
|
+
| -------------------- | ----------------------------------------------------------------------------- |
|
|
20
|
+
| `knowledge/` | Another agent — or you next week — cannot resume this. Nothing records _why_. |
|
|
21
|
+
| Changesets and plans | No build order, no per-piece proof. Fine at this size, painful past it. |
|
|
22
|
+
| Design direction | It will look like the template. |
|
|
23
|
+
| Refusal scenarios | Access control is asserted, not proven. |
|
|
24
24
|
|
|
25
25
|
**Say this out loud to the user, once, when you finish.** A quick build that gets
|
|
26
26
|
mistaken for a real one is the only way this mode does damage. §6 is the way out.
|
|
@@ -55,9 +55,9 @@ message catalogue only: add the locale and set `defaultLocale`, and leave
|
|
|
55
55
|
`baseLocale` at `en`. the app workflow's §1a has the three axes in full; getting
|
|
56
56
|
them confused is how a project ends up unable to add a second language.
|
|
57
57
|
|
|
58
|
-
## 2.
|
|
58
|
+
## 2. Virtual users — 60 seconds, not optional
|
|
59
59
|
|
|
60
|
-
`packages/functions/src/
|
|
60
|
+
`packages/functions/src/virtual-users.ts` ships with a `visitor`. Add one virtual user per
|
|
61
61
|
kind of person, plus **a second one of the primary kind** — that is what makes
|
|
62
62
|
"you see yours, not theirs" observable when you click around.
|
|
63
63
|
|
|
@@ -65,10 +65,14 @@ kind of person, plus **a second one of the primary kind** — that is what makes
|
|
|
65
65
|
rule, and it lives in each function's `permissions`, not in a role:
|
|
66
66
|
|
|
67
67
|
```typescript
|
|
68
|
-
import {
|
|
68
|
+
import { defineVirtualUsers } from '#pikku/scopes/pikku-virtual-users.gen.js'
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
visitor: {
|
|
70
|
+
defineVirtualUsers({
|
|
71
|
+
visitor: {
|
|
72
|
+
name: 'Visitor',
|
|
73
|
+
jobTitle: 'Synthetic health-check user',
|
|
74
|
+
account: {},
|
|
75
|
+
},
|
|
72
76
|
amina: { name: 'Amina', jobTitle: 'Gardener', account: {} },
|
|
73
77
|
bilal: { name: 'Bilal', jobTitle: 'Gardener', account: {} },
|
|
74
78
|
})
|
|
@@ -78,16 +82,28 @@ definePersonas({
|
|
|
78
82
|
named:
|
|
79
83
|
|
|
80
84
|
```typescript
|
|
81
|
-
import {
|
|
85
|
+
import { defineVirtualUsers } from '#pikku/scopes/pikku-virtual-users.gen.js'
|
|
82
86
|
import { defineSystemRole } from '#pikku/scopes'
|
|
83
87
|
|
|
84
88
|
defineSystemRole({
|
|
85
|
-
owner: {
|
|
86
|
-
|
|
89
|
+
owner: {
|
|
90
|
+
displayName: 'Owner',
|
|
91
|
+
description: 'Sees only their own rows',
|
|
92
|
+
scopes: [],
|
|
93
|
+
},
|
|
94
|
+
tenant: {
|
|
95
|
+
displayName: 'Tenant',
|
|
96
|
+
description: 'Sees only their own tenancy',
|
|
97
|
+
scopes: [],
|
|
98
|
+
},
|
|
87
99
|
})
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
visitor: {
|
|
101
|
+
defineVirtualUsers({
|
|
102
|
+
visitor: {
|
|
103
|
+
name: 'Visitor',
|
|
104
|
+
jobTitle: 'Synthetic health-check user',
|
|
105
|
+
account: {},
|
|
106
|
+
},
|
|
91
107
|
amina: { name: 'Amina', jobTitle: 'Owner', roles: ['owner'], account: {} },
|
|
92
108
|
bilal: { name: 'Bilal', jobTitle: 'Owner', roles: ['owner'], account: {} },
|
|
93
109
|
chidi: { name: 'Chidi', jobTitle: 'Tenant', roles: ['tenant'], account: {} },
|
|
@@ -97,10 +113,10 @@ definePersonas({
|
|
|
97
113
|
Two owners in both examples, deliberately: one owner cannot demonstrate that
|
|
98
114
|
owners are separated from each other.
|
|
99
115
|
|
|
100
|
-
- **Keep `visitor`.** The shipped scenarios name `
|
|
116
|
+
- **Keep `visitor`.** The shipped scenarios name `virtualUsers.visitor`; removing it
|
|
101
117
|
fails `pikku all` and nothing you write after that registers.
|
|
102
|
-
- **One `
|
|
103
|
-
- **Never write an email address** — each is derived from the
|
|
118
|
+
- **One `defineVirtualUsers` call for the whole project.**
|
|
119
|
+
- **Never write an email address** — each is derived from the virtual user id and
|
|
104
120
|
`scenarios.emailDomain` in `pikku.config.json`.
|
|
105
121
|
- `roles` is typechecked against `defineSystemRole`; an undeclared role is a
|
|
106
122
|
build error.
|
|
@@ -122,7 +138,7 @@ Then, in this order — it is the order codegen depends on:
|
|
|
122
138
|
the file is plain `INSERT`s — no `ON CONFLICT DO NOTHING`. It is local only:
|
|
123
139
|
no deploy applies it, so anything the app cannot run without belongs in a
|
|
124
140
|
migration instead. **Be generous, and
|
|
125
|
-
seed rows for both
|
|
141
|
+
seed rows for both virtual users.** An empty app demos badly, and you cannot see a
|
|
126
142
|
layout break against zero rows.
|
|
127
143
|
3. **Functions** — one `pikkuFunc` per `*.function.ts`, `expose: true`. Pikku
|
|
128
144
|
generates the typed RPC client and React Query hooks; you do NOT write HTTP
|
|
@@ -160,17 +176,15 @@ than they save:
|
|
|
160
176
|
keystroke now and a rewrite later.
|
|
161
177
|
- Never hardcode a host or port — the API base resolves to same-origin `/api`.
|
|
162
178
|
|
|
163
|
-
Before the first run, make sure `.env` holds
|
|
164
|
-
appends missing
|
|
179
|
+
Before the first run, make sure `.env` holds the local secret — `bun run dev`
|
|
180
|
+
appends it if missing, but an older scaffold or a copied `.env` may not have it:
|
|
165
181
|
|
|
166
182
|
```sh
|
|
167
183
|
grep -q '^BETTER_AUTH_SECRET=' .env 2>/dev/null || echo "BETTER_AUTH_SECRET=$(openssl rand -base64 32)" >> .env
|
|
168
|
-
grep -q '^SCENARIO_ACTOR_SECRET=' .env 2>/dev/null || echo "SCENARIO_ACTOR_SECRET=$(openssl rand -base64 32)" >> .env
|
|
169
184
|
```
|
|
170
185
|
|
|
171
186
|
Never commit `.env`. Then run it — through `bun run dev`, never `vite` on its
|
|
172
|
-
own, because the dev script is what
|
|
173
|
-
"Sign in as …" switcher; without it the switcher silently renders nothing:
|
|
187
|
+
own, because the dev script is what points the frontend at your API:
|
|
174
188
|
|
|
175
189
|
```sh
|
|
176
190
|
bun run prebuild && bun run dev
|
|
@@ -200,18 +214,31 @@ app a look — that is the app workflow's §8a.
|
|
|
200
214
|
|
|
201
215
|
## 5. One smoke scenario
|
|
202
216
|
|
|
203
|
-
Not the full ladder — one journey, end to end, as a real
|
|
217
|
+
Not the full ladder — one journey, end to end, as a real virtual user, so the app has
|
|
204
218
|
at least one thing that stays true.
|
|
205
219
|
|
|
206
220
|
```typescript
|
|
207
221
|
import { pikkuScenario } from '#pikku/scenarios'
|
|
208
222
|
|
|
209
|
-
export const ownerCreatesAndSeesItScenario = pikkuScenario<
|
|
223
|
+
export const ownerCreatesAndSeesItScenario = pikkuScenario<
|
|
224
|
+
void,
|
|
225
|
+
{ id: string }
|
|
226
|
+
>({
|
|
210
227
|
title: 'An owner creates a thing and sees it',
|
|
211
228
|
tags: ['scenario', 'smoke'],
|
|
212
|
-
func: async (_services, _data, { scenario,
|
|
213
|
-
const row = await scenario.do(
|
|
214
|
-
|
|
229
|
+
func: async (_services, _data, { scenario, virtualUsers }) => {
|
|
230
|
+
const row = await scenario.do(
|
|
231
|
+
'creates',
|
|
232
|
+
'createThing',
|
|
233
|
+
{ name: 'first' },
|
|
234
|
+
{ virtualUser: virtualUsers.amina }
|
|
235
|
+
)
|
|
236
|
+
await scenario.then(
|
|
237
|
+
'sees it listed',
|
|
238
|
+
'thingShowsInList',
|
|
239
|
+
{ id: row.id },
|
|
240
|
+
{ virtualUser: virtualUsers.amina }
|
|
241
|
+
)
|
|
215
242
|
return { id: row.id }
|
|
216
243
|
},
|
|
217
244
|
})
|
|
@@ -221,9 +248,9 @@ export const ownerCreatesAndSeesItScenario = pikkuScenario<void, { id: string }>
|
|
|
221
248
|
`pikkuScenarioStep`.** An RPC name in a `then` will not resolve.
|
|
222
249
|
- **Every scenario must assert.** A ladder with no `then` is a PKU680 critical —
|
|
223
250
|
it fails `pikku all`, stopping codegen rather than a test.
|
|
224
|
-
-
|
|
225
|
-
|
|
226
|
-
a reason that reads like an auth bug.
|
|
251
|
+
- **Run scenarios against a `pikku dev` server.** `pikku dev` creates the virtual
|
|
252
|
+
user signing key; without a running or previously started dev server every
|
|
253
|
+
scenario fails at sign-in, for a reason that reads like an auth bug.
|
|
227
254
|
- **There is no state reset** — scope what you create to unique ids.
|
|
228
255
|
|
|
229
256
|
Keep the three shipped scenarios in `packages/functions/test/scenarios/` green.
|
|
@@ -246,8 +273,8 @@ to the app workflow (the app reference, see SKILL.md) and do this, in order:
|
|
|
246
273
|
`decisions/` for what you chose silently, `questions/` for what you guessed
|
|
247
274
|
at. Then `pikku knowledge index && pikku knowledge validate`.
|
|
248
275
|
2. Backfill an entity note for each thing the app holds, so `pikku knowledge
|
|
249
|
-
|
|
250
|
-
3. Write the refusal scenarios — the ones proving one
|
|
276
|
+
gaps` has something to measure against.
|
|
277
|
+
3. Write the refusal scenarios — the ones proving one virtual user cannot reach
|
|
251
278
|
another's rows. This is the gap that matters most.
|
|
252
279
|
4. Then pick up the app workflow at its §4 (apps) or §5 (changes) for
|
|
253
280
|
anything new.
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
- Running them
|
|
12
12
|
- Coverage — which functions have actually been run
|
|
13
13
|
|
|
14
|
-
A scenario is a user journey run as one of your
|
|
15
|
-
|
|
14
|
+
A scenario is a user journey run as one of your virtual users, over the real transport, with that
|
|
15
|
+
virtual user's session. It is the only kind of test worth writing here, because a passing one proves the
|
|
16
16
|
app works the way a signed-in person experiences it.
|
|
17
17
|
|
|
18
18
|
The traps below all cost a real changeset a red run, and most of them are invisible on the run that
|
|
@@ -39,26 +39,27 @@ import { pikkuScenario } from '#pikku/scenarios'
|
|
|
39
39
|
|
|
40
40
|
export const tenantReportsAFaultScenario = pikkuScenario<void, { id: string }>({
|
|
41
41
|
title: 'A tenant reports a fault and the owner sees it',
|
|
42
|
-
description:
|
|
42
|
+
description:
|
|
43
|
+
'The report lands on the owning landlord’s queue, and nobody else’s',
|
|
43
44
|
tags: ['scenario', 'maintenance'],
|
|
44
|
-
func: async (_services, _data, { scenario,
|
|
45
|
+
func: async (_services, _data, { scenario, virtualUsers }) => {
|
|
45
46
|
const report = await scenario.do(
|
|
46
47
|
'reports a broken boiler',
|
|
47
48
|
'createMaintenanceReport',
|
|
48
49
|
{ summary: 'No hot water' },
|
|
49
|
-
{
|
|
50
|
+
{ virtualUser: virtualUsers.chidi }
|
|
50
51
|
)
|
|
51
52
|
await scenario.then(
|
|
52
53
|
'appears on the owner’s queue',
|
|
53
54
|
'reportShowsOnQueue',
|
|
54
55
|
{ id: report.id },
|
|
55
|
-
{
|
|
56
|
+
{ virtualUser: virtualUsers.amina }
|
|
56
57
|
)
|
|
57
58
|
await scenario.then(
|
|
58
59
|
'is invisible to the other owner',
|
|
59
60
|
'reportIsNotVisible',
|
|
60
61
|
{ id: report.id },
|
|
61
|
-
{
|
|
62
|
+
{ virtualUser: virtualUsers.bilal }
|
|
62
63
|
)
|
|
63
64
|
return { id: report.id }
|
|
64
65
|
},
|
|
@@ -70,10 +71,10 @@ export const tenantReportsAFaultScenario = pikkuScenario<void, { id: string }>({
|
|
|
70
71
|
(server-side by default, plus a `browser` one that drives the page). Reaching for an RPC name in a
|
|
71
72
|
`then` will not resolve.
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
for a reason that reads like an auth bug.
|
|
76
|
-
|
|
74
|
+
**Scenarios sign in with the key `pikku dev` created.** Start `pikku dev` once in the project (it writes
|
|
75
|
+
`.pikku-runtime/virtual-user/signing.key`); without that key every scenario fails at sign-in, before its
|
|
76
|
+
first step, for a reason that reads like an auth bug. Against a deployed stage export
|
|
77
|
+
`VIRTUAL_USER_SIGNING_KEY`, which that stage's deployer holds.
|
|
77
78
|
|
|
78
79
|
---
|
|
79
80
|
|
|
@@ -99,7 +100,7 @@ each half of that rule, because the first half does not imply the second.
|
|
|
99
100
|
|
|
100
101
|
**Write each scenario's setup out in full rather than sharing a local helper.** A helper holding
|
|
101
102
|
setup steps does not fail extraction — the steps are recorded and the suite goes green — but the
|
|
102
|
-
extractor cannot bind an `
|
|
103
|
+
extractor cannot bind an `virtualUser` that arrives as a function parameter, so the transcript credits
|
|
103
104
|
every setup step to whoever the last literal binding named. Seven permission scenarios here read
|
|
104
105
|
"henrik sets up her company Salon Nordlicht" in a suite whose whole point was that the company is
|
|
105
106
|
finja's. A scenario body is a recorded document, and the duplication is the price of it saying who
|
|
@@ -109,7 +110,7 @@ did what.
|
|
|
109
110
|
|
|
110
111
|
## What to assert
|
|
111
112
|
|
|
112
|
-
**Write the refusals, and assert WHY.** One
|
|
113
|
+
**Write the refusals, and assert WHY.** One virtual user reaching for another's row has to be rejected,
|
|
113
114
|
and that rejection is a scenario — it is how you prove access control instead of asserting it. But
|
|
114
115
|
only if the step reads the reason: "not ok" is also what a malformed request returns, so a refusal
|
|
115
116
|
step that stops at the status code passes on a call the function never even ran. One here posted
|
|
@@ -150,7 +151,7 @@ second run starts where its first one stopped.
|
|
|
150
151
|
**Run the suite twice and require the second run green.** A suite that only passes on a fresh
|
|
151
152
|
database is a suite that passes once. Two corollaries, both of which cost a changeset a red run:
|
|
152
153
|
|
|
153
|
-
- **Name nothing a setup step might already own.** `setsUpHerCompany` returns the company that
|
|
154
|
+
- **Name nothing a setup step might already own.** `setsUpHerCompany` returns the company that virtual user
|
|
154
155
|
already has rather than renaming it, so a later step passed the literal name it had asked for and
|
|
155
156
|
was told no such company exists. Read the name, slug or id back off the step's output and pass
|
|
156
157
|
THAT.
|
|
@@ -187,7 +188,7 @@ scenarios that had been green for months.
|
|
|
187
188
|
|
|
188
189
|
**A selector on IDENTITY alone rots the same way once rows gain a lifecycle.** One reused the first
|
|
189
190
|
licence assigned to an email regardless of its state, which was correct until a new changeset's
|
|
190
|
-
refusal scenarios left that
|
|
191
|
+
refusal scenarios left that virtual user holding cancelled ones — it then handed back a dead licence, read
|
|
191
192
|
as success, and failed a scenario two changesets older at a step that needed a live one.
|
|
192
193
|
|
|
193
194
|
**A step's input is a recorded contract, and it does not take a version.** Widening one — an extra
|
|
@@ -251,7 +252,7 @@ bunx --bun pikku scenario run local --spawn # server-side, the
|
|
|
251
252
|
bunx --bun pikku scenario run local --spawn --run browser # the same journeys, driven as a human
|
|
252
253
|
```
|
|
253
254
|
|
|
254
|
-
In a multi-app project that one run covers both frontends: each
|
|
255
|
+
In a multi-app project that one run covers both frontends: each virtual user carries
|
|
255
256
|
its own `app` and `@pikku/playwright` resolves the base url from the
|
|
256
257
|
environment's `appUrls` map, so there is no second environment to run.
|
|
257
258
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Shipping, and staying
|
|
1
|
+
# Shipping, and staying deploy-ready
|
|
2
2
|
|
|
3
3
|
## Contents
|
|
4
4
|
|
|
5
5
|
- Ship it — open source, no platform
|
|
6
|
-
- Staying
|
|
6
|
+
- Staying deploy-ready
|
|
7
7
|
|
|
8
8
|
Read this when the changes queue is empty and the scenarios are green — it is
|
|
9
9
|
the last phase, and nothing in it is needed before then.
|
|
@@ -86,7 +86,7 @@ Changing the strategy renames units, and a unit name is what a queue consumer, a
|
|
|
86
86
|
scheduled task and a `dependsOn` point at. The manifest rewrites all three, but
|
|
87
87
|
anything holding a unit name outside the manifest does not follow. Units dropped
|
|
88
88
|
from the manifest are not deleted by OSS `deploy()` either (pikkujs/pikku#543) —
|
|
89
|
-
|
|
89
|
+
a hosting platform may sweep them after each deploy, plain pikku leaves the
|
|
90
90
|
old workers in place.
|
|
91
91
|
|
|
92
92
|
`tags` matches the tags a function inherits from its wirings — `wireHTTP({ ...,
|
|
@@ -158,23 +158,22 @@ lint over function return types, catching a `Pii`/`Secret` field that leaks
|
|
|
158
158
|
through an exposed function. Expensive, so run it before a release rather than on
|
|
159
159
|
every save — but run it.
|
|
160
160
|
|
|
161
|
-
## Staying
|
|
161
|
+
## Staying deploy-ready
|
|
162
162
|
|
|
163
163
|
Everything above is open source. This is the contract that keeps
|
|
164
|
-
|
|
164
|
+
the project a one-command import for a hosting platform later, instead of a migration.
|
|
165
165
|
|
|
166
166
|
- **`frontends` in `pikku.config.json` describes reality.** Every app has an
|
|
167
167
|
entry with the right `cwd`, `kind` and `dev` (`command`, `port`); exactly one
|
|
168
|
-
is `primary`; `serves` and `
|
|
169
|
-
|
|
170
|
-
- **`
|
|
171
|
-
have the real id; the CLI writes it
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
frontend. Fabric materialises these as its virtual users; a persona nobody
|
|
168
|
+
is `primary`; `serves` and `virtualUsers` name real virtual users declared in
|
|
169
|
+
`defineVirtualUsers`.
|
|
170
|
+
- **`cloud.projectId` in `pikku.config.json` is optional.** Leave it out unless you
|
|
171
|
+
have the real id; the CLI writes it once the project is linked. Do not invent a value.
|
|
172
|
+
- **One `defineVirtualUsers` call**, every virtual user reachable through exactly one
|
|
173
|
+
frontend. A hosting platform may materialise these as its users; a virtual user nobody
|
|
175
174
|
serves imports as a person with no way in.
|
|
176
175
|
- **`knowledge/` passes `validate`, and `pikku knowledge gaps` lists nothing.** This is
|
|
177
|
-
the part
|
|
176
|
+
the part a hosting platform reads and continues from.
|
|
178
177
|
- **Every plan under `knowledge/plans/` passes `pikku knowledge plan progress`.**
|
|
179
178
|
A merged changeset is a claim; the plan reconciled against the generated meta
|
|
180
179
|
is the check. Anything the first pass still owes is either built now or deferred with
|
|
@@ -192,7 +191,6 @@ Everything above is open source. This is the contract that keeps
|
|
|
192
191
|
the SDK.
|
|
193
192
|
- **`pikku validate` is clean**, and `pikku all` has no critical diagnostics.
|
|
194
193
|
|
|
195
|
-
What you deliberately do NOT do:
|
|
196
|
-
|
|
197
|
-
is needed the day the user wants Fabric to host and build it, and on that day, if
|
|
194
|
+
What you deliberately do NOT do: link a project to a hosting platform. None of it is needed to build, test, critique, or deploy — it
|
|
195
|
+
is needed the day the user wants one to host and build it, and on that day, if
|
|
198
196
|
this section holds, that day is one command long.
|
|
@@ -11,8 +11,8 @@ Read this at the design step, once you have a direction to turn into a theme —
|
|
|
11
11
|
whether the user described one in words, handed you a reference, or ran their own
|
|
12
12
|
design step whose output you are implementing.
|
|
13
13
|
|
|
14
|
-
Nothing in the open-source toolchain authors a theme for you.
|
|
15
|
-
|
|
14
|
+
Nothing in the open-source toolchain authors a theme for you. A hosting
|
|
15
|
+
platform may offer a theme tool; without it, hand-authoring is the route, and it is a small
|
|
16
16
|
job done in JSON.
|
|
17
17
|
|
|
18
18
|
## Where the look lives
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-changes
|
|
3
|
-
description: 'Work a project''s changes queue — the todo list someone filed by circling things
|
|
4
|
-
installGroups: [fabric]
|
|
3
|
+
description: 'Work a project''s changes queue — the todo list someone filed by circling things in the running app. Covers `pikku changes wait|claim|show|ask|reply|shot|done`: waiting for work without polling, asking instead of guessing, saying why an item is left undone, offering options as images, one commit per item. TRIGGER when: the user says "run the changes against a stage", "work the changes (queue)", "watch the changes", "pick up the changes", names a change by its #number, or you are otherwise idle in a checkout with open changes (`pikku changes list`). DO NOT TRIGGER for git changes, diffs or changelogs, and not for deploying or debugging a stage.'
|
|
5
4
|
---
|
|
6
5
|
|
|
7
6
|
# Working a changes queue
|
|
@@ -44,7 +43,7 @@ something changed. `wait` does the waiting and exits only when there is work.
|
|
|
44
43
|
| ---- | ------------------------------------------------------ | ----------------------------------------------- |
|
|
45
44
|
| 0 | work printed (claimed, and/or `Answered`) | work it, then step 3 |
|
|
46
45
|
| 2 | `--timeout`/`--once` found nothing | stop, or restart `wait` |
|
|
47
|
-
| 3 |
|
|
46
|
+
| 3 | auth refused | relay the CLI's message; stop |
|
|
48
47
|
| 1 | anything else | report the message; stop |
|
|
49
48
|
|
|
50
49
|
3. For each item: `show` → fix → commit → `done`; or `ask` and move on; or `reply`
|
|
@@ -62,6 +61,25 @@ group's lease, done — and when a held or leased item is **claimable at** (a lo
|
|
|
62
61
|
cannot be taken. For held items, run `wait --claim` rather than retrying. The lease is 30 minutes
|
|
63
62
|
(`--lease-minutes`); an abandoned claim returns to the queue by itself.
|
|
64
63
|
|
|
64
|
+
## The board
|
|
65
|
+
|
|
66
|
+
Every change sits in one column: backlog, ready, in progress, needs answer, done.
|
|
67
|
+
`list` prints them left to right with counts (`--include-done` adds done). A claim is
|
|
68
|
+
not a column: it lives on the changeset's lease, so a change still in progress whose
|
|
69
|
+
lease lapsed shows as `abandoned`, and `next` and `wait` pick it up again.
|
|
70
|
+
|
|
71
|
+
`claim`, `wait` and `next` never take a backlog change; `claim` on one is refused
|
|
72
|
+
with a 409. Move it first, and only when the person asked for it to be worked:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pikku changes ready 5 # backlog → ready
|
|
76
|
+
pikku changes backlog 5 # shelve it again
|
|
77
|
+
pikku changes start 3 4 # ready → in progress, the work has begun
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The backlog lives in the local queue only; a project whose changes are in Pikku
|
|
81
|
+
Studio Cloud refuses `backlog`, `ready` and `file --backlog`.
|
|
82
|
+
|
|
65
83
|
## Changesets
|
|
66
84
|
|
|
67
85
|
Group related items into changesets and claim each one as a group, saying which tables it touches —
|
|
@@ -187,5 +205,3 @@ Branch and commit default to the checkout you are in — run it there, never typ
|
|
|
187
205
|
the item's commit by its `Change-Id` trailer, so close items in any order.
|
|
188
206
|
An item you decided not to do is not `done`: `reply` with why and leave it for a
|
|
189
207
|
human to dismiss.
|
|
190
|
-
|
|
191
|
-
Registering writes with Fabric needs the `changes:project:write` scope.
|