@pikku/skills 0.12.54 → 0.12.55

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 (54) hide show
  1. package/README.md +1 -1
  2. package/dist/skills.gen.js +2 -2
  3. package/dist/snippets.js +1 -1
  4. package/package.json +1 -1
  5. package/skills/pikku-architect/SKILL.md +1 -1
  6. package/skills/pikku-architect/references/plan-defects.md +7 -7
  7. package/skills/pikku-auth/references/better-auth.md +92 -96
  8. package/skills/pikku-build/SKILL.md +9 -9
  9. package/skills/pikku-build/references/app.md +56 -61
  10. package/skills/pikku-build/references/design.md +14 -14
  11. package/skills/pikku-build/references/feature.md +3 -3
  12. package/skills/pikku-build/references/mocks.md +6 -0
  13. package/skills/pikku-build/references/multi-app.md +16 -16
  14. package/skills/pikku-build/references/openapi.md +34 -34
  15. package/skills/pikku-build/references/platform.md +4 -5
  16. package/skills/pikku-build/references/post-clone.md +1 -1
  17. package/skills/pikku-build/references/quick.md +63 -36
  18. package/skills/pikku-build/references/scenarios.md +17 -16
  19. package/skills/pikku-build/references/ship.md +14 -16
  20. package/skills/pikku-build/references/theming.md +2 -2
  21. package/skills/pikku-changes/SKILL.md +2 -5
  22. package/skills/pikku-concepts/SKILL.md +26 -27
  23. package/skills/pikku-concepts/references/language.md +6 -6
  24. package/skills/pikku-deploy/references/nextjs.md +1 -1
  25. package/skills/pikku-guide/SKILL.md +18 -17
  26. package/skills/pikku-i18n/references/messages.md +5 -6
  27. package/skills/pikku-knowledge/SKILL.md +3 -3
  28. package/skills/pikku-meta/references/audit.md +1 -1
  29. package/skills/pikku-permissions/SKILL.md +1 -1
  30. package/skills/pikku-react/SKILL.md +18 -20
  31. package/skills/pikku-react/references/client.md +2 -37
  32. package/skills/pikku-report/SKILL.md +10 -10
  33. package/skills/pikku-scenario/SKILL.md +63 -66
  34. package/skills/pikku-scenario/references/browser.md +5 -5
  35. package/skills/pikku-scenario/references/coverage.md +4 -4
  36. package/skills/pikku-scenario/references/steps.md +31 -30
  37. package/skills/pikku-scenario/references/{persona-run.md → virtual-user-run.md} +38 -43
  38. package/skills/pikku-scenario/references/virtual-users.md +114 -0
  39. package/skills/pikku-seo/SKILL.md +1 -1
  40. package/skills/pikku-services/references/audit-wire-service.md +2 -2
  41. package/skills/pikku-services/references/audit.md +1 -2
  42. package/skills/pikku-services/references/services.md +1 -1
  43. package/skills/pikku-software-archaeology/SKILL.md +13 -13
  44. package/skills/pikku-software-archaeology/references/blueprint.schema.json +9 -9
  45. package/skills/pikku-theme/SKILL.md +1 -1
  46. package/skills/pikku-workflow/SKILL.md +1 -1
  47. package/skills/pikku-admin-to-fabric/SKILL.md +0 -198
  48. package/skills/pikku-blueprint-to-fabric/SKILL.md +0 -362
  49. package/skills/pikku-blueprint-to-fabric/scripts/inventory.mjs +0 -367
  50. package/skills/pikku-fabric/SKILL.md +0 -331
  51. package/skills/pikku-fabric/references/database.md +0 -128
  52. package/skills/pikku-fabric/references/debugging.md +0 -114
  53. package/skills/pikku-fabric/references/deploy.md +0 -179
  54. 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… | 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 |
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 | 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 |
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, actors carrying it into scenarios — is
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
- 'sign-in'`) or the Connect screen (`reauth: 'connect'`), not a generic error.
117
+ 'sign-in'`) or the Connect screen (`reauth: 'connect'`), not a generic error.
118
118
 
119
119
  ## Scenarios
120
120
 
121
- A persona that signs in through the upstream needs an upstream credential to
122
- act with. The install adds `credentials` to `pikkuActor` in `src/auth.ts`, and
123
- at sign-in each actor stores `ACTOR_CREDENTIAL_<PERSONA>_<NAME>` from the
124
- environment (e.g. `ACTOR_CREDENTIAL_SALES_REP_DOLIBARR`). Without it every
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/personas.md` has the details.
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, personas and roles,
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, Fabric-readiness. This file adds the surfaces that turn an app into a
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
- persona, and asserts the end state. `pikku-react` covers driving it
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
- persona's screen changed without a reload.
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, OSS or Fabric, starting
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. Personas — 60 seconds, not optional
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 | 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. |
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. Personas — 60 seconds, not optional
58
+ ## 2. Virtual users — 60 seconds, not optional
59
59
 
60
- `packages/functions/src/personas.ts` ships with a `visitor`. Add one persona per
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 { definePersonas } from '#pikku/scopes/pikku-personas.gen.js'
68
+ import { defineVirtualUsers } from '#pikku/scopes/pikku-virtual-users.gen.js'
69
69
 
70
- definePersonas({
71
- visitor: { name: 'Visitor', jobTitle: 'Synthetic health-check user', account: {} },
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 { definePersonas } from '#pikku/scopes/pikku-personas.gen.js'
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: { displayName: 'Owner', description: 'Sees only their own rows', scopes: [] },
86
- tenant: { displayName: 'Tenant', description: 'Sees only their own tenancy', scopes: [] },
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
- definePersonas({
90
- visitor: { name: 'Visitor', jobTitle: 'Synthetic health-check user', account: {} },
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 `actors.visitor`; removing it
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 `definePersonas` call for the whole project.**
103
- - **Never write an email address** — each is derived from the persona id and
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 personas.** An empty app demos badly, and you cannot see a
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 both local secrets — `bun run dev`
164
- appends missing ones, but an older scaffold or a copied `.env` may not have them:
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 hands the frontend the persona list for the
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 persona, so the app has
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<void, { id: string }>({
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, actors }) => {
213
- const row = await scenario.do('creates', 'createThing', { name: 'first' }, { actor: actors.amina })
214
- await scenario.then('sees it listed', 'thingShowsInList', { id: row.id }, { actor: actors.amina })
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
- - **`SCENARIO_ACTOR_SECRET` must be in `.env`** (above). Without it
225
- `/api/auth/sign-in/actor` is disabled and every scenario fails at sign-in, for
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
- gaps` has something to measure against.
250
- 3. Write the refusal scenarios — the ones proving one persona cannot reach
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 personas, over the real transport, with that
15
- persona's session. It is the only kind of test worth writing here, because a passing one proves the
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: 'The report lands on the owning landlord’s queue, and nobody else’s',
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, actors }) => {
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
- { actor: actors.chidi },
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
- { actor: actors.amina },
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
- { actor: actors.bilal },
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
- **`SCENARIO_ACTOR_SECRET` must be in `.env`** (the app workflow's §6, before the first run). Without it
74
- `/api/auth/sign-in/actor` is disabled — every scenario then fails at sign-in, before its first step,
75
- for a reason that reads like an auth bug. `pikku scenario run` reads it from the environment, so
76
- source `.env` first (`set -a && . ./.env && set +a`) when you run outside `bun run dev`.
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 `actor` that arrives as a function parameter, so the transcript credits
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 persona reaching for another's row has to be rejected,
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 actor
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 actor holding cancelled ones — it then handed back a dead licence, read
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 persona carries
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 Fabric-ready
1
+ # Shipping, and staying deploy-ready
2
2
 
3
3
  ## Contents
4
4
 
5
5
  - Ship it — open source, no platform
6
- - Staying Fabric-ready
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
- fabric sweeps its dispatch namespace after each deploy, plain pikku leaves the
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 Fabric-ready
161
+ ## Staying deploy-ready
162
162
 
163
163
  Everything above is open source. This is the contract that keeps
164
- `pikku fabric init` a one-command import later, instead of a migration.
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 `personas` name real personas from the personas
169
- section.
170
- - **`fabric.projectId` in `pikku.config.json` is optional.** Leave it out unless you
171
- have the real id; the CLI writes it after finding the project from the git
172
- remote. Do not invent a value.
173
- - **One `definePersonas` call**, every persona reachable through exactly one
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 Fabric itself reads and continues from.
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: run any `pikku fabric` subcommand, add a card,
196
- or link a project. None of it is needed to build, test, critique, or deploy — it
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. Fabric has the
15
- `fabric-theme` tool; without it, hand-authoring is the route, and it is a small
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 on a deployed stage. 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 pikkufabric changes", "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 — use pikku-fabric for those.'
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 | session refused | tell the user to run `pikku fabric login`; stop |
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`
@@ -187,5 +186,3 @@ Branch and commit default to the checkout you are in — run it there, never typ
187
186
  the item's commit by its `Change-Id` trailer, so close items in any order.
188
187
  An item you decided not to do is not `done`: `reply` with why and leave it for a
189
188
  human to dismiss.
190
-
191
- Registering writes with Fabric needs the `changes:project:write` scope.