@pikku/inspector 0.12.103 → 0.12.106

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 (62) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/add/add-cli.js +21 -11
  3. package/dist/add/add-functions.js +8 -8
  4. package/dist/add/add-virtual-users.d.ts +13 -0
  5. package/dist/add/{add-personas.js → add-virtual-users.js} +33 -33
  6. package/dist/add/add-workflow.js +14 -10
  7. package/dist/error-codes.d.ts +4 -3
  8. package/dist/error-codes.js +4 -3
  9. package/dist/inspector.js +5 -3
  10. package/dist/types.d.ts +3 -3
  11. package/dist/utils/extract-function-name.js +1 -1
  12. package/dist/utils/get-property-value.js +1 -1
  13. package/dist/utils/load-addon-functions-meta.js +1 -0
  14. package/dist/utils/post-process.d.ts +36 -36
  15. package/dist/utils/post-process.js +198 -173
  16. package/dist/utils/serialize-inspector-state.d.ts +2 -2
  17. package/dist/utils/serialize-inspector-state.js +6 -6
  18. package/dist/utils/single-declaration.d.ts +3 -3
  19. package/dist/utils/single-declaration.js +3 -3
  20. package/dist/utils/validate-single-step-workflows.d.ts +3 -0
  21. package/dist/utils/validate-single-step-workflows.js +32 -0
  22. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +3 -3
  23. package/dist/utils/workflow/dsl/patterns.d.ts +3 -3
  24. package/dist/utils/workflow/dsl/patterns.js +4 -4
  25. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +6 -6
  26. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +5 -5
  27. package/dist/visit.js +2 -2
  28. package/knowledge/decisions/a-channel-with-auth-false-marks-its-connect-and-disconnect-sessionless.md +22 -0
  29. package/knowledge/decisions/a-declared-scope-id-never-shadows-a-definition.md +20 -0
  30. package/knowledge/decisions/a-dsl-step-virtual-user-is-the-literal-name-in-its-options.md +27 -0
  31. package/knowledge/decisions/a-feature-flags-anyof-is-read-from-the-ast-and-an-empty-list-is-refused.md +20 -0
  32. package/knowledge/decisions/a-non-literal-string-array-reads-as-absent.md +25 -0
  33. package/knowledge/decisions/a-permission-with-an-underscore-data-parameter-takes-no-data.md +20 -0
  34. package/knowledge/decisions/a-provider-login-virtual-user-is-never-runnable.md +27 -0
  35. package/knowledge/decisions/a-remote-addon-bound-credential-or-secret-must-be-wired.md +27 -0
  36. package/knowledge/decisions/a-route-auth-overrides-its-groups-auth.md +22 -0
  37. package/knowledge/decisions/a-route-requires-a-session-when-any-auth-signal-says-so.md +29 -0
  38. package/knowledge/decisions/a-webhook-source-with-verify-gets-a-singleton-secret-credential.md +21 -0
  39. package/knowledge/decisions/a-wire-with-auth-disabled-cannot-point-at-a-pikkufunc.md +21 -0
  40. package/knowledge/decisions/addon-contracts-cannot-wire-or-declare-handlers.md +25 -0
  41. package/knowledge/decisions/addon-declarations-name-the-secrets-and-credentials-the-app-lends-it.md +19 -0
  42. package/knowledge/decisions/an-inline-wiring-function-is-sessionless-unless-built-with-pikkufunc.md +23 -0
  43. package/knowledge/decisions/better-auth-admin-plugin-is-refused.md +24 -0
  44. package/knowledge/decisions/define-screens-scopes-must-be-string-literals.md +18 -0
  45. package/knowledge/decisions/index.md +38 -0
  46. package/knowledge/decisions/inspector-records-a-non-literal-global-grant-as-source-text.md +20 -0
  47. package/knowledge/decisions/only-one-pikku-better-auth-export-per-codebase.md +19 -0
  48. package/knowledge/decisions/scope-references-and-secret-aliases-are-critical-diagnostics.md +23 -0
  49. package/knowledge/decisions/scope-segments-may-not-be-wildcards-or-contain-colons.md +23 -0
  50. package/knowledge/decisions/shorthand-permission-entries-are-extracted.md +22 -0
  51. package/knowledge/decisions/system-roles-declare-literal-unqualified-scopes.md +28 -0
  52. package/knowledge/decisions/the-inspector-records-agent-permissions-only-to-aggregate-services.md +14 -0
  53. package/knowledge/decisions/vault-secrets-reach-no-sink-and-pii-stays-out-of-logs-and-webhooks.md +30 -0
  54. package/knowledge/decisions/webhook-gateway-wrappers-are-sessionless-and-gateway-auth-runs-inside-the-handler.md +26 -0
  55. package/knowledge/index.md +14 -0
  56. package/knowledge/questions/does-a-non-literal-remote-addon-auth-get-checked.md +26 -0
  57. package/knowledge/questions/index.md +16 -0
  58. package/knowledge/questions/is-an-incomplete-oauth2-block-dropped-without-a-log.md +23 -0
  59. package/knowledge/questions/should-app-functions-named-like-scenario-instrumentation-be-refused.md +28 -0
  60. package/knowledge/questions/should-the-openapi-document-default-every-operation-to-a-bearer-or-api-key.md +22 -0
  61. package/package.json +3 -3
  62. package/dist/add/add-personas.d.ts +0 -13
package/CHANGELOG.md CHANGED
@@ -1,3 +1,53 @@
1
+ ## 0.12.106
2
+
3
+ ### Patch Changes
4
+
5
+ - fdcc821: Fabric is gone from the open toolchain. The previous names are removed with no aliases.
6
+
7
+ The CLI group is `pikku cloud …` and the export is `@pikku/cli/cloud`. The project link is the `cloud` key of `pikku.config.json` (`cloud.projectId`), the CLI keeps its local state in `~/.pikkustudio` (run `pikku cloud login` again), and the project config file is `pikkustudio.config.json`.
8
+
9
+ Every environment variable drops the `FABRIC_` prefix for `PIKKUSTUDIO_` with the same suffix: `PIKKUSTUDIO_API_URL`, `PIKKUSTUDIO_PROJECT_ID`, `PIKKUSTUDIO_TOKEN`, `PIKKUSTUDIO_OPERATOR_TOKEN`, `PIKKUSTUDIO_AUTH_PUBLIC_KEY`, `PIKKUSTUDIO_STAGE_ID` and the rest. Rename them wherever you set them.
10
+
11
+ Wire ids: the operator sign-in route is `/sign-in/cloud`, the plugin and mode literal is `'cloud'`, the Better Auth plugin is `pikkuCloud`, its session field is `pikkustudioadmin`, and the signature header is `x-cloud-sig`. The cloud API calls are `getCloudLogs`, `getCloudLogsByStageKind`, `cloudCliProjects`, `cloudCliDeployments`, `providedByCloud` and `recordCloudAnalyticsEvents`. Addon packages are `@pikkustudio/*`, scaffolded files live under `scaffold/cloud` and `__cloud_*__`, and the API hosts are the `pikkustudio.app` and `pikkustudio.dev` domains.
12
+
13
+ `pikku skills install --fabric` is `--cloud`, the install group is `cloud`, and the skills are `pikku-cloud`, `pikku-cloud-debug`, `pikku-admin-to-cloud` and `pikku-blueprint-to-cloud`.
14
+
15
+ The stale analytics forwarder copies are removed from the online-shop example and the starter template.
16
+
17
+ The sealed-box salt stays `pikkufabric/sealed-box/v1`: it is a cryptographic constant that already-sealed secrets depend on.
18
+
19
+ - 33b102d: Virtual users now have one name everywhere. The id column of `virtual_user_run` and `virtual_user_schedule` is `virtual_user_id` (a migration renames it in place), the CLI group is `pikku virtual-user …`, and the declarations and imports live under `@pikku/core/virtual-user`. The previous names are removed with no aliases.
20
+
21
+ The default virtual user email domain is now `virtualuser.invalid`: apps that rely on the default domain get new accounts at the next provisioning (`pikku virtual-user sync`, deploy, or `pikku dev` start), and the old rows stay unused.
22
+
23
+ The sign-in surface carries the same name. The route is `/sign-in/virtual-user`, the environment variables are `PIKKU_ALLOW_VIRTUAL_USER_SIGN_IN` and the `PIKKU_DEV_VIRTUAL_USER_SIGN_IN` / `VITE_*` equivalents, the Better Auth plugin is `pikkuVirtualUser`, the scenario step option and wire member are `virtualUser` and `virtualUsers`, and the Better Auth user flag column is `virtual_user`. Rename the environment variables wherever you set them, and add a migration that renames the flag column on the `user` table. Nothing is aliased.
24
+
25
+ The conversation runner is now `@pikku/core/virtual-user/conversation`, replacing the old `-flow` subpath. Update the import; the old subpath is gone.
26
+
27
+ - Updated dependencies [fdcc821]
28
+ - Updated dependencies [33b102d]
29
+ - Updated dependencies [6a25c70]
30
+ - Updated dependencies [a084c3b]
31
+ - @pikku/core@0.12.142
32
+
33
+ ## 0.12.105
34
+
35
+ ### Patch Changes
36
+
37
+ - b04e444: Fix CLI commands that come from an addon package. Auth and permissions set on such a command are now merged into the addon's own function instead of replacing it, and the command's input is shaped by the addon's own schema (for example a comma list becomes an array). The inspector now also resolves `...refCLI('addon:contract')`, spreads and `defineCLICommands` identifiers inside a command's `subcommands`.
38
+ - Updated dependencies [b04e444]
39
+ - Updated dependencies [d3d8a98]
40
+ - Updated dependencies [658da50]
41
+ - @pikku/core@0.12.141
42
+
43
+ ## 0.12.104
44
+
45
+ ### Patch Changes
46
+
47
+ - 5464b86: Add the `pikku verify` command over `@pikku/code-edit/verify`: codegen, the backend and frontend type-checks and static correctness checks as structured findings with fix hints. The inspector warns about single-step workflows (PKU644) and PKU111 now names the file and line.
48
+ - Updated dependencies [f396538]
49
+ - @pikku/core@0.12.140
50
+
1
51
  ## 0.12.103
2
52
 
3
53
  ### Patch Changes
@@ -325,15 +325,23 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
325
325
  }
326
326
  break;
327
327
  case 'subcommands': {
328
- let subcommandsNode = prop.initializer;
329
- if (ts.isIdentifier(prop.initializer)) {
330
- subcommandsNode = resolveIdentifier(prop.initializer, typeChecker, [
331
- 'defineCLICommands',
332
- ]);
333
- }
334
- if (subcommandsNode && ts.isObjectLiteralExpression(subcommandsNode)) {
335
- meta.subcommands = {};
336
- for (const subProp of subcommandsNode.properties) {
328
+ const subcommands = {};
329
+ const collect = (node) => {
330
+ const refContract = resolveRefContract(node, 'refCLI', inspectorState.exportedContracts.addonCli);
331
+ if (refContract) {
332
+ Object.assign(subcommands, refContract.contract);
333
+ return;
334
+ }
335
+ const target = ts.isIdentifier(node)
336
+ ? resolveIdentifier(node, typeChecker, ['defineCLICommands'])
337
+ : node;
338
+ if (!target || !ts.isObjectLiteralExpression(target))
339
+ return;
340
+ for (const subProp of target.properties) {
341
+ if (ts.isSpreadAssignment(subProp)) {
342
+ collect(subProp.expression);
343
+ continue;
344
+ }
337
345
  if (!ts.isPropertyAssignment(subProp))
338
346
  continue;
339
347
  const subName = getPropertyName(subProp);
@@ -341,10 +349,12 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
341
349
  continue;
342
350
  const subCommand = processCommand(logger, inspectorState, options, subName, subProp.initializer, sourceFile, typeChecker, programName, fullPath, programTags);
343
351
  if (subCommand) {
344
- meta.subcommands[subName] = subCommand;
352
+ subcommands[subName] = subCommand;
345
353
  }
346
354
  }
347
- }
355
+ };
356
+ collect(prop.initializer);
357
+ meta.subcommands = subcommands;
348
358
  break;
349
359
  }
350
360
  case 'isDefault':
@@ -21,7 +21,7 @@ const SCENARIO_SURFACE_KEYS = [
21
21
  * copied into an addon package by a consumer, a called identifier cannot.
22
22
  */
23
23
  const SCENARIO_STEP_KINDS = new Map([
24
- ['pikkuScenarioStep', 'persona'],
24
+ ['pikkuScenarioStep', 'virtualUser'],
25
25
  ['pikkuPlatformScenarioStep', 'platform'],
26
26
  ['pikkuAddonScenarioStep', 'addon'],
27
27
  ]);
@@ -424,7 +424,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
424
424
  let workflowRetries;
425
425
  let workflowTimeout;
426
426
  let scenarioStepSurfaces;
427
- let scenarioStepRequiresActor;
427
+ let scenarioStepRequiresVirtualUser;
428
428
  let scenarioStepAddon;
429
429
  let scenarioStepTemplate;
430
430
  let scopes;
@@ -560,9 +560,9 @@ export const addFunctions = (logger, node, checker, state, options) => {
560
560
  ? declaredSurfaces
561
561
  : undefined;
562
562
  // A browser binding cannot be provisioned without a window to open, so it
563
- // requires an actor whether or not the step said so.
564
- scenarioStepRequiresActor =
565
- getPropertyValue(firstArg, 'actor') === true ||
563
+ // requires a virtual user whether or not the step said so.
564
+ scenarioStepRequiresVirtualUser =
565
+ getPropertyValue(firstArg, 'virtualUser') === true ||
566
566
  declaredSurfaces.includes('browser')
567
567
  ? true
568
568
  : undefined;
@@ -1083,10 +1083,10 @@ export const addFunctions = (logger, node, checker, state, options) => {
1083
1083
  workflowTimeout: workflowTimeout ?? undefined,
1084
1084
  compensate: compensateInitializer ? true : undefined,
1085
1085
  scenarioStepSurfaces,
1086
- scenarioStepRequiresActor,
1087
- // `persona` is the default reading of an unmarked step, so it is left off
1086
+ scenarioStepRequiresVirtualUser,
1087
+ // `virtualUser` is the default reading of an unmarked step, so it is left off
1088
1088
  // rather than written into every step meta a project already has.
1089
- scenarioStepKind: scenarioStepKind && scenarioStepKind !== 'persona'
1089
+ scenarioStepKind: scenarioStepKind && scenarioStepKind !== 'virtualUser'
1090
1090
  ? scenarioStepKind
1091
1091
  : undefined,
1092
1092
  scenarioStepAddon,
@@ -0,0 +1,13 @@
1
+ import type { AddWiring } from '../types.js';
2
+ /**
3
+ * Inspector for `defineVirtualUsers()` calls.
4
+ *
5
+ * A virtual user is entirely declaration — a person, their roles, what they want —
6
+ * so there is no code body and nothing to resolve at runtime. What is read here
7
+ * is the whole thing, and it is generated straight to `virtual-users.gen.json` for
8
+ * the CLI and the console to read without loading the app.
9
+ *
10
+ * `name` is the one field that cannot be defaulted. A virtual user without one is
11
+ * not a person, and the console has nothing to show.
12
+ */
13
+ export declare const addVirtualUsers: AddWiring;
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  import { DISPOSITIONS } from '@pikku/core/virtual-user';
3
- import { isRunnablePersona } from '@pikku/core/persona';
3
+ import { isRunnableVirtualUser } from '@pikku/core/virtual-user';
4
4
  import { ErrorCode } from '../error-codes.js';
5
5
  import { claimSingleDeclaration } from '../utils/single-declaration.js';
6
6
  import { booleanProperty, getProperty, numberProperty, stringArrayProperty, stringProperty, unwrapAs, } from '../utils/literal-properties.js';
@@ -15,7 +15,7 @@ const RATES = [
15
15
  *
16
16
  * The ranges are checked here rather than at run time because these are the
17
17
  * kind of typo that does not throw: `repeatRate: 18` meaning 18% would silently
18
- * double every call the persona makes, and you would read the run as a product
18
+ * double every call the virtual user makes, and you would read the run as a product
19
19
  * bug.
20
20
  */
21
21
  const tuningProperty = (config, onError) => {
@@ -72,7 +72,7 @@ const accountProperty = (value) => {
72
72
  const provider = stringProperty(value, 'provider');
73
73
  return provider === undefined ? {} : { provider };
74
74
  };
75
- const linkedAccountsProperty = (config, personaId, logger) => {
75
+ const linkedAccountsProperty = (config, virtualUserId, logger) => {
76
76
  const value = getProperty(config, 'linkedAccounts');
77
77
  if (!value || !ts.isObjectLiteralExpression(value)) {
78
78
  return undefined;
@@ -83,12 +83,12 @@ const linkedAccountsProperty = (config, personaId, logger) => {
83
83
  continue;
84
84
  }
85
85
  if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
86
- logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, `Persona '${personaId}' has a linked account whose key is not a literal.`);
86
+ logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, `VirtualUser '${virtualUserId}' has a linked account whose key is not a literal.`);
87
87
  continue;
88
88
  }
89
89
  const account = accountProperty(prop.initializer);
90
90
  if (!account) {
91
- logger.critical(ErrorCode.INVALID_VALUE, `Persona '${personaId}' linked account '${prop.name.text}' must be an object literal.`);
91
+ logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${virtualUserId}' linked account '${prop.name.text}' must be an object literal.`);
92
92
  continue;
93
93
  }
94
94
  accounts[prop.name.text] = account;
@@ -96,22 +96,22 @@ const linkedAccountsProperty = (config, personaId, logger) => {
96
96
  return Object.keys(accounts).length ? accounts : undefined;
97
97
  };
98
98
  /**
99
- * Inspector for `definePersonas()` calls.
99
+ * Inspector for `defineVirtualUsers()` calls.
100
100
  *
101
- * A persona is entirely declaration — a person, their roles, what they want —
101
+ * A virtual user is entirely declaration — a person, their roles, what they want —
102
102
  * so there is no code body and nothing to resolve at runtime. What is read here
103
- * is the whole thing, and it is generated straight to `personas.gen.json` for
103
+ * is the whole thing, and it is generated straight to `virtual-users.gen.json` for
104
104
  * the CLI and the console to read without loading the app.
105
105
  *
106
- * `name` is the one field that cannot be defaulted. A persona without one is
106
+ * `name` is the one field that cannot be defaulted. A virtual user without one is
107
107
  * not a person, and the console has nothing to show.
108
108
  */
109
- export const addPersonas = (logger, node, _checker, state) => {
109
+ export const addVirtualUsers = (logger, node, _checker, state) => {
110
110
  if (!ts.isCallExpression(node)) {
111
111
  return;
112
112
  }
113
113
  const expression = node.expression;
114
- if (!ts.isIdentifier(expression) || expression.text !== 'definePersonas') {
114
+ if (!ts.isIdentifier(expression) || expression.text !== 'defineVirtualUsers') {
115
115
  return;
116
116
  }
117
117
  const firstArg = node.arguments[0];
@@ -120,11 +120,11 @@ export const addPersonas = (logger, node, _checker, state) => {
120
120
  }
121
121
  const unwrapped = unwrapAs(firstArg);
122
122
  if (!ts.isObjectLiteralExpression(unwrapped)) {
123
- logger.critical(ErrorCode.INVALID_VALUE, 'definePersonas() needs an inline object — personas are read statically, never evaluated.');
123
+ logger.critical(ErrorCode.INVALID_VALUE, 'defineVirtualUsers() needs an inline object — virtual users are read statically, never evaluated.');
124
124
  return;
125
125
  }
126
126
  const sourceFile = node.getSourceFile().fileName;
127
- if (!claimSingleDeclaration(logger, state.personas.files, ErrorCode.DUPLICATE_PERSONAS_DEFINITION, 'definePersonas', sourceFile)) {
127
+ if (!claimSingleDeclaration(logger, state.virtualUsers.files, ErrorCode.DUPLICATE_VIRTUAL_USERS_DEFINITION, 'defineVirtualUsers', sourceFile)) {
128
128
  return;
129
129
  }
130
130
  const known = Object.keys(DISPOSITIONS);
@@ -133,46 +133,46 @@ export const addPersonas = (logger, node, _checker, state) => {
133
133
  continue;
134
134
  }
135
135
  if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
136
- logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, 'A persona is declared with a key that is not a literal.');
136
+ logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, 'A virtual user is declared with a key that is not a literal.');
137
137
  continue;
138
138
  }
139
139
  const id = prop.name.text;
140
140
  if (!ts.isObjectLiteralExpression(prop.initializer)) {
141
- logger.critical(ErrorCode.INVALID_VALUE, `Persona '${id}' must be an object literal.`);
141
+ logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${id}' must be an object literal.`);
142
142
  continue;
143
143
  }
144
144
  const config = prop.initializer;
145
145
  const name = stringProperty(config, 'name');
146
146
  if (!name) {
147
- logger.critical(ErrorCode.INVALID_VALUE, `Persona '${id}' needs a literal 'name' — a persona is a person, and the console has nothing to show without one.`);
147
+ logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${id}' needs a literal 'name' — a virtual user is a person, and the console has nothing to show without one.`);
148
148
  continue;
149
149
  }
150
- // The generated `definePersonas` types this as a union, so a bad value only
150
+ // The generated `defineVirtualUsers` types this as a union, so a bad value only
151
151
  // reaches here in source that does not compile. Caught anyway: a
152
152
  // disposition is the entire behaviour, and defaulting a typo to `realistic`
153
153
  // would run a different person than the one that was written.
154
154
  const disposition = stringProperty(config, 'disposition');
155
155
  if (disposition !== undefined && !known.includes(disposition)) {
156
- logger.critical(ErrorCode.INVALID_VALUE, `Persona '${id}' has an unknown disposition '${disposition}'. One of: ${known.join(', ')}`);
156
+ logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${id}' has an unknown disposition '${disposition}'. One of: ${known.join(', ')}`);
157
157
  continue;
158
158
  }
159
159
  let tuningFailed = false;
160
160
  const tuning = tuningProperty(config, (message) => {
161
161
  tuningFailed = true;
162
- logger.critical(ErrorCode.INVALID_VALUE, `Persona '${id}': ${message}`);
162
+ logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${id}': ${message}`);
163
163
  });
164
164
  if (tuningFailed) {
165
165
  continue;
166
166
  }
167
167
  const account = accountProperty(getProperty(config, 'account'));
168
168
  const declaredRunnable = booleanProperty(config, 'runnable');
169
- const persona = {
169
+ const virtualUser = {
170
170
  id,
171
171
  name,
172
172
  roles: stringArrayProperty(config, 'roles') ?? [],
173
173
  goals: stringArrayProperty(config, 'goals') ?? [],
174
174
  tags: stringArrayProperty(config, 'tags') ?? [],
175
- runnable: isRunnablePersona({
175
+ runnable: isRunnableVirtualUser({
176
176
  ...(declaredRunnable !== undefined
177
177
  ? { runnable: declaredRunnable }
178
178
  : {}),
@@ -182,38 +182,38 @@ export const addPersonas = (logger, node, _checker, state) => {
182
182
  };
183
183
  const jobTitle = stringProperty(config, 'jobTitle');
184
184
  if (jobTitle !== undefined)
185
- persona.jobTitle = jobTitle;
185
+ virtualUser.jobTitle = jobTitle;
186
186
  const description = stringProperty(config, 'description');
187
187
  if (description !== undefined)
188
- persona.description = description;
188
+ virtualUser.description = description;
189
189
  const avatarUrl = stringProperty(config, 'avatarUrl');
190
190
  if (avatarUrl !== undefined)
191
- persona.avatarUrl = avatarUrl;
191
+ virtualUser.avatarUrl = avatarUrl;
192
192
  const personality = stringProperty(config, 'personality');
193
193
  if (personality !== undefined)
194
- persona.personality = personality;
194
+ virtualUser.personality = personality;
195
195
  const app = stringProperty(config, 'app');
196
196
  if (app !== undefined)
197
- persona.app = app;
197
+ virtualUser.app = app;
198
198
  if (disposition !== undefined) {
199
- persona.disposition = disposition;
199
+ virtualUser.disposition = disposition;
200
200
  }
201
201
  if (tuning !== undefined)
202
- persona.tuning = tuning;
202
+ virtualUser.tuning = tuning;
203
203
  const fixtures = stringArrayProperty(config, 'fixtures');
204
204
  if (fixtures !== undefined)
205
- persona.fixtures = fixtures;
205
+ virtualUser.fixtures = fixtures;
206
206
  // Left absent rather than resolved here: "everywhere but production" is a
207
207
  // fact about the config, and the inspector has no business baking today's
208
208
  // environment list into a meta file that outlives it.
209
209
  const environments = stringArrayProperty(config, 'environments');
210
210
  if (environments !== undefined)
211
- persona.environments = environments;
211
+ virtualUser.environments = environments;
212
212
  if (account !== undefined)
213
- persona.account = account;
213
+ virtualUser.account = account;
214
214
  const linkedAccounts = linkedAccountsProperty(config, id, logger);
215
215
  if (linkedAccounts !== undefined)
216
- persona.linkedAccounts = linkedAccounts;
217
- state.personas.definitions.push(persona);
216
+ virtualUser.linkedAccounts = linkedAccounts;
217
+ state.virtualUsers.definitions.push(virtualUser);
218
218
  }
219
219
  };
@@ -1,11 +1,12 @@
1
1
  import * as ts from 'typescript';
2
+ import { relative } from 'node:path';
2
3
  import { extractFunctionName } from '../utils/extract-function-name.js';
3
4
  import { extractFunctionNode } from '../utils/extract-function-node.js';
4
5
  import { ErrorCode } from '../error-codes.js';
5
6
  import { extractStringLiteral, isStringLike, isFunctionLike, extractDescription, extractDuration, } from '../utils/extract-node-value.js';
6
7
  import { getCommonWireMetaData, getPropertyValue, } from '../utils/get-property-value.js';
7
8
  import { extractDSLWorkflow } from '../utils/workflow/dsl/extract-dsl-workflow.js';
8
- import { getSourceText, extractActorFromOptions, } from '../utils/workflow/dsl/patterns.js';
9
+ import { getSourceText, extractVirtualUserFromOptions, } from '../utils/workflow/dsl/patterns.js';
9
10
  /**
10
11
  * Extract a workflow step's display name without letting a non-static name
11
12
  * (e.g. a function call) abort the scan. The step name is cosmetic, so a
@@ -141,7 +142,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
141
142
  type: 'rpc',
142
143
  stepName,
143
144
  rpcName,
144
- actor: extractActorFromOptions(optionsArg),
145
+ virtualUser: extractVirtualUserFromOptions(optionsArg),
145
146
  });
146
147
  state.rpc.invokedFunctions.add(rpcName);
147
148
  }
@@ -164,7 +165,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
164
165
  stepName,
165
166
  rpcName,
166
167
  expectEventually: true,
167
- actor: extractActorFromOptions(args.length >= 5 ? args[4] : undefined),
168
+ virtualUser: extractVirtualUserFromOptions(args.length >= 5 ? args[4] : undefined),
168
169
  });
169
170
  state.rpc.invokedFunctions.add(rpcName);
170
171
  }
@@ -229,7 +230,7 @@ export const addWorkflow = (logger, node, checker, state, options) => {
229
230
  wrapperType = 'complex';
230
231
  }
231
232
  else if (expression.text === 'pikkuScenario') {
232
- // A scenario is a complex workflow whose steps run as actors over the
233
+ // A scenario is a complex workflow whose steps run as virtual users over the
233
234
  // real transport — same extraction rules as complex, distinct meta.
234
235
  wrapperType = 'scenario';
235
236
  }
@@ -243,7 +244,10 @@ export const addWorkflow = (logger, node, checker, state, options) => {
243
244
  const { pikkuFuncId, name, exportedName } = extractFunctionName(node, checker, state.rootDir);
244
245
  const workflowName = exportedName || name;
245
246
  if (!workflowName) {
246
- logger.critical(ErrorCode.MISSING_NAME, `Could not determine workflow name from export.`);
247
+ const sourceFile = node.getSourceFile();
248
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
249
+ logger.critical(ErrorCode.MISSING_NAME, `Could not determine workflow name from export at ${relative(state.rootDir, sourceFile.fileName)}:${line + 1}. ` +
250
+ `Assign the \`${expression.text}(...)\` call to an exported const; that name is how it is registered.`);
247
251
  return;
248
252
  }
249
253
  // Extract the function node (either direct function or from config.func)
@@ -358,11 +362,11 @@ export const addWorkflow = (logger, node, checker, state, options) => {
358
362
  const sink = steps.length > 0 ? [] : steps;
359
363
  getWorkflowInvocations(resolvedFunc, checker, state, workflowName, sink);
360
364
  }
361
- // Actor names the flow's steps run as (scenarios) — powers the console
362
- // personas view and the Scenario graph badges.
363
- const actorNames = [
365
+ // Virtual user names the flow's steps run as (scenarios) — powers the console
366
+ // virtual users view and the Scenario graph badges.
367
+ const virtualUserNames = [
364
368
  ...new Set(steps
365
- .map((s) => ('actor' in s ? s.actor : undefined))
369
+ .map((s) => ('virtualUser' in s ? s.virtualUser : undefined))
366
370
  .filter((a) => typeof a === 'string')),
367
371
  ];
368
372
  state.workflows.meta[workflowName] = {
@@ -380,7 +384,7 @@ export const addWorkflow = (logger, node, checker, state, options) => {
380
384
  skip,
381
385
  scenario: wrapperType === 'scenario' ? true : undefined,
382
386
  asserts,
383
- actors: actorNames.length > 0 ? actorNames : undefined,
387
+ virtualUsers: virtualUserNames.length > 0 ? virtualUserNames : undefined,
384
388
  };
385
389
  // The scenario "logger/config only" rule (PKU673) and the implicit
386
390
  // workflow platform-service injection both used to live here, reading
@@ -20,16 +20,17 @@ export declare enum ErrorCode {
20
20
  CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = "PKU672",
21
21
  SCENARIO_HAS_SERVICES = "PKU673",
22
22
  EXPECT_EVENTUALLY_SCENARIO_ONLY = "PKU675",
23
- SCENARIO_STEP_NEEDS_ACTOR = "PKU677",
23
+ SCENARIO_STEP_NEEDS_VIRTUAL_USER = "PKU677",
24
24
  SCENARIO_STEP_TARGET_NOT_STATIC = "PKU678",
25
25
  SCENARIO_NOT_EXTRACTABLE = "PKU679",
26
26
  SCENARIO_HAS_NO_ASSERTION = "PKU680",
27
- SCENARIO_STEP_PROSE_NAMES_ACTOR = "PKU681",
27
+ SCENARIO_STEP_PROSE_NAMES_VIRTUAL_USER = "PKU681",
28
28
  SCENARIO_HAS_NO_FEATURE = "PKU682",
29
29
  WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = "PKU600",
30
30
  INVALID_DSL_WORKFLOW = "PKU641",
31
31
  WORKFLOW_GRAPH_ADDON_NOT_WIRED = "PKU642",
32
32
  COMPLEX_WORKFLOW_NOT_ALLOWED = "PKU643",
33
+ SINGLE_STEP_WORKFLOW = "PKU644",
33
34
  REMOTE_ADDON_NOT_DEV_DEPENDENCY = "PKU338",
34
35
  REMOTE_ADDON_AUTH_UNRESOLVED = "PKU339",
35
36
  ADDON_NOT_INSTALLED = "PKU340",
@@ -53,7 +54,7 @@ export declare enum ErrorCode {
53
54
  FUNCTION_METADATA_NOT_FOUND = "PKU559",
54
55
  DUPLICATE_AUTH_DEFINITION = "PKU581",
55
56
  AUTH_NOT_EXPORTED = "PKU582",
56
- DUPLICATE_PERSONAS_DEFINITION = "PKU585",
57
+ DUPLICATE_VIRTUAL_USERS_DEFINITION = "PKU585",
57
58
  ROUTE_PARAM_MISMATCH = "PKU571",
58
59
  ROUTE_QUERY_MISMATCH = "PKU572",
59
60
  AUTH_DISABLED_REQUIRES_SESSIONLESS = "PKU573",
@@ -22,16 +22,17 @@ export var ErrorCode;
22
22
  ErrorCode["CLI_CLIENTSIDE_RENDERER_HAS_SERVICES"] = "PKU672";
23
23
  ErrorCode["SCENARIO_HAS_SERVICES"] = "PKU673";
24
24
  ErrorCode["EXPECT_EVENTUALLY_SCENARIO_ONLY"] = "PKU675";
25
- ErrorCode["SCENARIO_STEP_NEEDS_ACTOR"] = "PKU677";
25
+ ErrorCode["SCENARIO_STEP_NEEDS_VIRTUAL_USER"] = "PKU677";
26
26
  ErrorCode["SCENARIO_STEP_TARGET_NOT_STATIC"] = "PKU678";
27
27
  ErrorCode["SCENARIO_NOT_EXTRACTABLE"] = "PKU679";
28
28
  ErrorCode["SCENARIO_HAS_NO_ASSERTION"] = "PKU680";
29
- ErrorCode["SCENARIO_STEP_PROSE_NAMES_ACTOR"] = "PKU681";
29
+ ErrorCode["SCENARIO_STEP_PROSE_NAMES_VIRTUAL_USER"] = "PKU681";
30
30
  ErrorCode["SCENARIO_HAS_NO_FEATURE"] = "PKU682";
31
31
  ErrorCode["WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED"] = "PKU600";
32
32
  ErrorCode["INVALID_DSL_WORKFLOW"] = "PKU641";
33
33
  ErrorCode["WORKFLOW_GRAPH_ADDON_NOT_WIRED"] = "PKU642";
34
34
  ErrorCode["COMPLEX_WORKFLOW_NOT_ALLOWED"] = "PKU643";
35
+ ErrorCode["SINGLE_STEP_WORKFLOW"] = "PKU644";
35
36
  // Remote addon (wireRemoteAddon) validation
36
37
  ErrorCode["REMOTE_ADDON_NOT_DEV_DEPENDENCY"] = "PKU338";
37
38
  ErrorCode["REMOTE_ADDON_AUTH_UNRESOLVED"] = "PKU339";
@@ -65,7 +66,7 @@ export var ErrorCode;
65
66
  // scopes and roles held this rule until the generated `admin` tree made it
66
67
  // unsatisfiable, and get it back once `admin` is a default scope nobody
67
68
  // declares. Do not reuse those two numbers for anything else.
68
- ErrorCode["DUPLICATE_PERSONAS_DEFINITION"] = "PKU585";
69
+ ErrorCode["DUPLICATE_VIRTUAL_USERS_DEFINITION"] = "PKU585";
69
70
  // HTTP Route errors
70
71
  ErrorCode["ROUTE_PARAM_MISMATCH"] = "PKU571";
71
72
  ErrorCode["ROUTE_QUERY_MISMATCH"] = "PKU572";
package/dist/inspector.js CHANGED
@@ -9,9 +9,10 @@ import { findCommonAncestor } from './utils/find-root-dir.js';
9
9
  import { createSourceFileCacheHost } from './source-file-cache.js';
10
10
  import { createNestedProjectFilter } from './utils/nested-project-filter.js';
11
11
  import { toProgramPath } from './utils/program-path.js';
12
- import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateAgentScorerReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateAddonUses, validateAddonContentGrants, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes, validatePersonaRoles, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, validateNoSecretAliasServices, registerDerivedOAuth2AppSecrets, validateSecretUsage, computeDiagnostics, validateSchemaReferences, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateScenarioFeatures, validateWorkflowGraphAddons, } from './utils/post-process.js';
12
+ import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateAgentScorerReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateAddonUses, validateAddonContentGrants, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes, validateVirtualUserRoles, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, validateNoSecretAliasServices, registerDerivedOAuth2AppSecrets, validateSecretUsage, computeDiagnostics, validateSchemaReferences, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateScenarioFeatures, validateWorkflowGraphAddons, } from './utils/post-process.js';
13
13
  import { validateExposedFunctionsGated } from './utils/validate-exposed-functions-gated.js';
14
14
  import { validateTagsResolveToMiddleware } from './utils/validate-tags-resolve-to-middleware.js';
15
+ import { validateSingleStepWorkflows } from './utils/validate-single-step-workflows.js';
15
16
  import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js';
16
17
  import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
17
18
  import { pikkuState } from '@pikku/core/state';
@@ -169,7 +170,7 @@ export function getInitialInspectorState(rootDir) {
169
170
  definitions: [],
170
171
  files: new Set(),
171
172
  },
172
- personas: {
173
+ virtualUsers: {
173
174
  definitions: [],
174
175
  files: new Set(),
175
176
  },
@@ -408,6 +409,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
408
409
  computeDiagnostics(state);
409
410
  validateSchemaWiringSeparation(logger, state);
410
411
  validateWorkflowGraphAddons(logger, state);
412
+ validateSingleStepWorkflows(logger, state);
411
413
  validateScenarioServices(logger, state);
412
414
  validateScenarioSteps(logger, state);
413
415
  validateScenarioFeatures(logger, state);
@@ -434,7 +436,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
434
436
  applyDeclaredScopes(state);
435
437
  validateScopeReferences(logger, state);
436
438
  validateSystemRoleScopes(logger, state);
437
- validatePersonaRoles(logger, state);
439
+ validateVirtualUserRoles(logger, state);
438
440
  logger.endValidationPass?.();
439
441
  }
440
442
  const cpu = process.cpuUsage(cpuStart);
package/dist/types.d.ts CHANGED
@@ -19,7 +19,7 @@ import type { CredentialDefinitions } from '@pikku/core/credential';
19
19
  import type { ScopeDefinitions } from '@pikku/core/scope';
20
20
  import type { SystemRoleDefinitions } from '@pikku/core/role';
21
21
  import type { FeatureFlagDefinitions } from '@pikku/core/flag';
22
- import type { PersonaDefinitions } from '@pikku/core/persona';
22
+ import type { VirtualUserDefinitions } from '@pikku/core/virtual-user';
23
23
  import type { VariableDefinitions } from '@pikku/core/variable';
24
24
  import type { TypesMap } from './types-map.js';
25
25
  import type { FunctionsMeta } from '@pikku/core/services';
@@ -784,8 +784,8 @@ export interface InspectorState {
784
784
  definitions: FeatureFlagDefinitions;
785
785
  files: Set<string>;
786
786
  };
787
- personas: {
788
- definitions: PersonaDefinitions;
787
+ virtualUsers: {
788
+ definitions: VirtualUserDefinitions;
789
789
  files: Set<string>;
790
790
  };
791
791
  variables: {
@@ -30,7 +30,7 @@ export function funcIdToTypeName(id) {
30
30
  }
31
31
  /**
32
32
  * The step wrappers, whichever kind of subject acts in them. A scenario calls a
33
- * platform or addon step by name exactly as it calls a persona one.
33
+ * platform or addon step by name exactly as it calls a virtual user one.
34
34
  */
35
35
  const SCENARIO_STEP_WRAPPERS = new Set([
36
36
  'pikkuScenarioStep',
@@ -102,7 +102,7 @@ export const assertStringLiteralProperty = (obj, propertyName, wiringType, logge
102
102
  * that anything written as an expression comes through raw: a `description`
103
103
  * split across lines as `'Fetch the thing ' + 'from upstream'` reaches
104
104
  * `infra.json` with the quotes and the `+` still in it, and is rendered that
105
- * way in the fabric console.
105
+ * way in the cloud console.
106
106
  *
107
107
  * Deliberately checker-free, unlike `extractStringLiteral`. Every value this
108
108
  * resolves is a literal in the same expression, so no symbol resolution is
@@ -45,6 +45,7 @@ const applyPackageToCLICommands = (commands, packageName, namespace) => {
45
45
  }
46
46
  if (namespace &&
47
47
  typeof command.pikkuFuncId === 'string' &&
48
+ command.pikkuFuncId !== '' &&
48
49
  !command.pikkuFuncId.includes(':')) {
49
50
  command.pikkuFuncId = `${namespace}:${command.pikkuFuncId}`;
50
51
  }