@pikku/cli 0.12.93 → 0.12.96

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 (180) hide show
  1. package/console-app/assets/{index-CmAplHjZ.js → index-CeQodYfg.js} +149 -149
  2. package/console-app/assets/{index-BYj7ZbUU.css → index-CyYHTGWZ.css} +1 -1
  3. package/console-app/index.html +2 -2
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-channel.d.ts +13 -15
  8. package/dist/.pikku/cli/pikku-cli-channel.js +62 -3
  9. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +14 -4
  10. package/dist/.pikku/cli/pikku-cli-client.gen.js +20 -10
  11. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  13. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  14. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  15. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +129 -2
  17. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  18. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  19. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  20. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  21. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  22. package/dist/.pikku/function/pikku-function-types.gen.d.ts +55 -20
  23. package/dist/.pikku/function/pikku-function-types.gen.js +27 -1
  24. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  25. package/dist/.pikku/function/pikku-functions-meta.gen.json +138 -13
  26. package/dist/.pikku/function/pikku-functions.gen.js +11 -1
  27. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  28. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  29. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  30. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  31. package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +1 -1
  32. package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +1 -1
  33. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  34. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  35. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  36. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  37. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  38. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.js +1 -1
  40. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  42. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  45. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  46. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +7 -0
  47. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
  48. package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
  49. package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
  50. package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
  51. package/dist/.pikku/scenarios/schemas/register.gen.d.ts +1 -1
  52. package/dist/.pikku/scenarios/schemas/register.gen.js +1 -1
  53. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  54. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  55. package/dist/.pikku/schemas/register.gen.js +13 -3
  56. package/dist/.pikku/schemas/schemas/PersonaRunInput.schema.json +1 -0
  57. package/dist/.pikku/schemas/schemas/PersonaSyncInput.schema.json +1 -0
  58. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  59. package/dist/.pikku/schemas/schemas/PikkuPersonasInput.schema.json +1 -0
  60. package/dist/.pikku/schemas/schemas/PikkuRolesInput.schema.json +1 -0
  61. package/dist/.pikku/schemas/schemas/RolesAuditInput.schema.json +1 -0
  62. package/dist/.pikku/schemas/schemas/RolesPruneInput.schema.json +1 -0
  63. package/dist/.pikku/scopes/pikku-personas-meta.gen.json +1 -0
  64. package/dist/.pikku/scopes/pikku-personas.gen.d.ts +36 -0
  65. package/dist/.pikku/scopes/pikku-personas.gen.js +17 -0
  66. package/dist/.pikku/scopes/pikku-roles-meta.gen.json +1 -0
  67. package/dist/.pikku/scopes/pikku-roles.gen.d.ts +30 -0
  68. package/dist/.pikku/scopes/pikku-roles.gen.js +18 -0
  69. package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +4 -2
  70. package/dist/.pikku/scopes/pikku-scope-types.gen.js +3 -2
  71. package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
  72. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +2 -2
  73. package/dist/.pikku/secrets/pikku-secret-types.gen.js +2 -2
  74. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  75. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  76. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  77. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  78. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +2 -2
  79. package/dist/.pikku/variables/pikku-variable-types.gen.js +2 -2
  80. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  81. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  82. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +27 -3
  83. package/dist/.pikku/workflow/pikku-personas.gen.d.ts +15 -0
  84. package/dist/.pikku/workflow/pikku-personas.gen.js +17 -0
  85. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +96 -19
  86. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +22 -2
  87. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  88. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  89. package/dist/bin/pikku-bin.mjs +2 -2
  90. package/dist/bin/pikku.js +0 -0
  91. package/dist/src/cli.wiring.js +101 -2
  92. package/dist/src/deploy/analyzer/analyzer.js +14 -0
  93. package/dist/src/deploy/analyzer/manifest.d.ts +11 -0
  94. package/dist/src/fabric/functions/validate.function.js +4 -4
  95. package/dist/src/functions/commands/bootstrap.js +3 -0
  96. package/dist/src/functions/commands/dev-ai-runner.d.ts +16 -4
  97. package/dist/src/functions/commands/dev-ai-runner.js +63 -29
  98. package/dist/src/functions/commands/dev.js +9 -4
  99. package/dist/src/functions/commands/enable.js +15 -19
  100. package/dist/src/functions/commands/{scenario-environment.d.ts → environment.d.ts} +9 -6
  101. package/dist/src/functions/commands/{scenario-environment.js → environment.js} +5 -4
  102. package/dist/src/functions/commands/new-addon.js +12 -12
  103. package/dist/src/functions/commands/persona-sync.d.ts +33 -0
  104. package/dist/src/functions/commands/persona-sync.js +151 -0
  105. package/dist/src/functions/commands/persona.d.ts +58 -0
  106. package/dist/src/functions/commands/persona.js +217 -0
  107. package/dist/src/functions/commands/roles-audit.d.ts +1 -0
  108. package/dist/src/functions/commands/roles-audit.js +37 -0
  109. package/dist/src/functions/commands/roles-prune.d.ts +16 -0
  110. package/dist/src/functions/commands/roles-prune.js +52 -0
  111. package/dist/src/functions/commands/roles-shared.d.ts +40 -0
  112. package/dist/src/functions/commands/roles-shared.js +77 -0
  113. package/dist/src/functions/commands/scenario-browser.d.ts +3 -3
  114. package/dist/src/functions/commands/scenario-browser.js +1 -1
  115. package/dist/src/functions/commands/scenario-ladder.d.ts +44 -2
  116. package/dist/src/functions/commands/scenario-ladder.js +51 -2
  117. package/dist/src/functions/commands/scenario.d.ts +7 -0
  118. package/dist/src/functions/commands/scenario.js +79 -17
  119. package/dist/src/functions/commands/scopes-shared.d.ts +8 -0
  120. package/dist/src/functions/commands/scopes-shared.js +1 -1
  121. package/dist/src/functions/commands/serve.js +7 -3
  122. package/dist/src/functions/commands/virtual-user-formatter.d.ts +30 -0
  123. package/dist/src/functions/commands/virtual-user-formatter.js +65 -0
  124. package/dist/src/functions/db/local-db.js +38 -1
  125. package/dist/src/functions/validate/workspace-validate.js +1 -1
  126. package/dist/src/functions/wirings/ai-agent/pikku-command-public-agent.js +2 -1
  127. package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +1 -0
  128. package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +2 -2
  129. package/dist/src/functions/wirings/auth/serialize-auth-gen.js +8 -8
  130. package/dist/src/functions/wirings/cli/serialize-channel-cli-client.d.ts +7 -3
  131. package/dist/src/functions/wirings/cli/serialize-channel-cli-client.js +32 -18
  132. package/dist/src/functions/wirings/cli/serialize-channel-cli.js +63 -6
  133. package/dist/src/functions/wirings/console/serialize-console-functions.js +4 -7
  134. package/dist/src/functions/wirings/functions/serialize-function-types.js +88 -28
  135. package/dist/src/functions/wirings/middleware/serialize-middleware-imports.js +25 -9
  136. package/dist/src/functions/wirings/package/serialize-package-types.js +5 -3
  137. package/dist/src/functions/wirings/personas/pikku-command-personas.d.ts +7 -0
  138. package/dist/src/functions/wirings/personas/pikku-command-personas.js +43 -0
  139. package/dist/src/functions/wirings/personas/serialize-personas-types.d.ts +18 -0
  140. package/dist/src/functions/wirings/personas/serialize-personas-types.js +76 -0
  141. package/dist/src/functions/wirings/realtime/pikku-command-events-scaffold.js +2 -1
  142. package/dist/src/functions/wirings/roles/pikku-command-roles.d.ts +7 -0
  143. package/dist/src/functions/wirings/roles/pikku-command-roles.js +33 -0
  144. package/dist/src/functions/wirings/roles/serialize-roles-types.d.ts +14 -0
  145. package/dist/src/functions/wirings/roles/serialize-roles-types.js +55 -0
  146. package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +2 -1
  147. package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +3 -3
  148. package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +3 -3
  149. package/dist/src/functions/wirings/scopes/serialize-scopes-types.d.ts +1 -1
  150. package/dist/src/functions/wirings/scopes/serialize-scopes-types.js +1 -1
  151. package/dist/src/functions/wirings/user-admin/pikku-command-user-admin-functions.js +2 -1
  152. package/dist/src/functions/wirings/user-admin/serialize-user-admin-functions.js +2 -2
  153. package/dist/src/functions/wirings/workflow/pikku-command-workflow-routes.js +2 -1
  154. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +13 -13
  155. package/dist/src/functions/wirings/workflow/serialize-personas.d.ts +10 -0
  156. package/dist/src/functions/wirings/workflow/serialize-personas.js +46 -0
  157. package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +11 -2
  158. package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +165 -29
  159. package/dist/src/functions/workflows/all.workflow.js +7 -1
  160. package/dist/src/server/content-signing-jwt.d.ts +13 -0
  161. package/dist/src/server/content-signing-jwt.js +51 -0
  162. package/dist/src/server/dev-server-runner.interface.d.ts +10 -2
  163. package/dist/src/server/node-server-runner.d.ts +2 -2
  164. package/dist/src/server/node-server-runner.js +2 -1
  165. package/dist/src/services.d.ts +3 -1
  166. package/dist/src/services.js +2 -1
  167. package/dist/src/utils/pikku-cli-config.js +45 -2
  168. package/dist/src/utils/resolve-personas.d.ts +32 -0
  169. package/dist/src/utils/resolve-personas.js +46 -0
  170. package/dist/src/utils/resolve-scaffold-feature.d.ts +19 -0
  171. package/dist/src/utils/resolve-scaffold-feature.js +49 -0
  172. package/dist/tsconfig.tsbuildinfo +1 -1
  173. package/package.json +17 -14
  174. package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +0 -1
  175. package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +0 -19
  176. package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +0 -17
  177. package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +0 -2
  178. package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +0 -40
  179. package/dist/src/utils/resolve-scenario-actors.d.ts +0 -23
  180. package/dist/src/utils/resolve-scenario-actors.js +0 -74
@@ -0,0 +1,33 @@
1
+ import { pikkuSessionlessFunc } from '#pikku';
2
+ import { writeFileInDir } from '../../../utils/file-writer.js';
3
+ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
4
+ import { serializeRolesTypes } from './serialize-roles-types.js';
5
+ import { validateAndBuildSystemRoleDefinitionsMeta } from '@pikku/core/role';
6
+ export const pikkuRoles = pikkuSessionlessFunc({
7
+ func: async ({ logger, config, getInspectorState }, data) => {
8
+ const { rolesFile, rolesMetaJsonFile } = config;
9
+ if (!rolesFile) {
10
+ return;
11
+ }
12
+ // Same cold-start reasoning as Scopes: on a bare .pikku this runs before
13
+ // pikku-types.gen.ts exists, so it takes the zero state. The file only has
14
+ // to exist so personas can import SystemRoleName; the real Roles step
15
+ // regenerates it with the declarations once setup has run.
16
+ const bootstrap = data?.bootstrap ?? false;
17
+ const state = await getInspectorState(false, bootstrap, bootstrap);
18
+ const content = serializeRolesTypes({
19
+ definitions: state.systemRoles.definitions,
20
+ });
21
+ await writeFileInDir(logger, rolesFile, content);
22
+ if (rolesMetaJsonFile) {
23
+ const meta = validateAndBuildSystemRoleDefinitionsMeta(state.systemRoles.definitions);
24
+ await writeFileInDir(logger, rolesMetaJsonFile, JSON.stringify(meta, null, 2));
25
+ }
26
+ },
27
+ middleware: [
28
+ logCommandInfoAndTime({
29
+ commandStart: 'Creating PikkuRoles types',
30
+ commandEnd: 'Created PikkuRoles types',
31
+ }),
32
+ ],
33
+ });
@@ -0,0 +1,14 @@
1
+ import type { SystemRoleDefinitions } from '@pikku/core/role';
2
+ export interface SerializeRolesOptions {
3
+ definitions: SystemRoleDefinitions;
4
+ }
5
+ /**
6
+ * Generates the `SystemRoleName` union and role metadata from
7
+ * `defineSystemRole` declarations.
8
+ *
9
+ * The union is what makes a persona's `roles: ['buyer']` checkable. Custom
10
+ * roles are deliberately absent: an admin can delete one from the console, so
11
+ * a persona pinned to it would silently stop testing what it claims to — the
12
+ * failure would be a scatter of 403s rather than a build error.
13
+ */
14
+ export declare const serializeRolesTypes: ({ definitions }: SerializeRolesOptions) => string;
@@ -0,0 +1,55 @@
1
+ const quote = (value) => `'${value.replace(/'/g, "\\'")}'`;
2
+ /**
3
+ * Generates the `SystemRoleName` union and role metadata from
4
+ * `defineSystemRole` declarations.
5
+ *
6
+ * The union is what makes a persona's `roles: ['buyer']` checkable. Custom
7
+ * roles are deliberately absent: an admin can delete one from the console, so
8
+ * a persona pinned to it would silently stop testing what it claims to — the
9
+ * failure would be a scatter of 403s rather than a build error.
10
+ */
11
+ export const serializeRolesTypes = ({ definitions }) => {
12
+ const names = [...new Set(definitions.map((d) => d.name))].sort();
13
+ const union = names.length > 0
14
+ ? `\n${names.map((name) => ` | ${quote(name)}`).join('\n')}`
15
+ : ' never';
16
+ return `/**
17
+ * This file was generated by the @pikku/cli
18
+ */
19
+ import type { SystemRole, SystemRoleDefinitionsMeta } from '@pikku/core/role'
20
+ import { flattenSystemRoleDefinitions } from '@pikku/core/role'
21
+ import definitions from './pikku-roles-meta.gen.json' with { type: 'json' }
22
+
23
+ /**
24
+ * Every role that ships with this application.
25
+ *
26
+ * Roles an admin composes in the console are not here and cannot be: they do
27
+ * not exist at build time, and can be deleted after it.
28
+ */
29
+ export type SystemRoleName =${union}
30
+
31
+ /**
32
+ * The declared role set, for a ScopeService to sync into its store.
33
+ *
34
+ * Derived from the metadata sidecar rather than inlined, so the import above
35
+ * forces tsc to emit the .json alongside this file — an addon publishes only
36
+ * its compiled output, and a host reads that .json to merge the addon's roles
37
+ * into its own union.
38
+ *
39
+ * Additive on removal: a deleted declaration leaves an inert row rather than
40
+ * revoking everyone's grant on deploy. Use \`pikku roles prune\` to remove them.
41
+ */
42
+ export const SYSTEM_ROLES: SystemRole[] = flattenSystemRoleDefinitions(
43
+ Object.values(definitions as SystemRoleDefinitionsMeta)
44
+ )
45
+
46
+ /** Declared roles, keyed by name. Rendered by the console when granting. */
47
+ export const SYSTEM_ROLES_META: Record<string, SystemRole> = Object.fromEntries(
48
+ SYSTEM_ROLES.map((role) => [role.name, role])
49
+ )
50
+
51
+ /** Whether a role name is declared in code, and therefore not editable. */
52
+ export const isSystemRole = (name: string): boolean =>
53
+ Object.prototype.hasOwnProperty.call(SYSTEM_ROLES_META, name)
54
+ `;
55
+ };
@@ -4,6 +4,7 @@ import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
5
  import { removeLegacyScaffoldFile } from '../../../utils/remove-legacy-scaffold-file.js';
6
6
  import { serializePublicRPC } from './serialize-public-rpc.js';
7
+ import { resolveScaffoldFeature } from '../../../utils/resolve-scaffold-feature.js';
7
8
  export const pikkuPublicRPC = pikkuSessionlessFunc({
8
9
  func: async ({ logger, config, variables }) => {
9
10
  const deployCodegenFlag = await variables.get('PIKKU_DEPLOY_CODEGEN');
@@ -12,7 +13,7 @@ export const pikkuPublicRPC = pikkuSessionlessFunc({
12
13
  }
13
14
  if (config.scaffold?.rpc && config.publicRpcSchemasFile) {
14
15
  const pathToPikkuTypes = getFileImportRelativePath(config.publicRpcFile, config.typesDeclarationFile, config.packageMappings);
15
- const { schemas, functions } = serializePublicRPC(pathToPikkuTypes, config.scaffold.rpc === 'auth', config.globalHTTPPrefix || '');
16
+ const { schemas, functions } = serializePublicRPC(pathToPikkuTypes, resolveScaffoldFeature('rpc', config.scaffold.rpc).auth, config.globalHTTPPrefix || '');
16
17
  await writeFileInDir(logger, config.publicRpcSchemasFile, schemas);
17
18
  await writeFileInDir(logger, config.publicRpcFile, functions);
18
19
  await removeLegacyScaffoldFile(config.publicRpcFile);
@@ -3,8 +3,8 @@
3
3
  * scenario runner can reach it over `/rpc/<name>` exactly as before.
4
4
  *
5
5
  * They are registered sessionless: `auth` decides whether a session is
6
- * *required*, and a sessioned function would demand one regardless of the flag
7
- * which is what made `scaffold.scenarios: true` (as opposed to `'auth'`) refuse
8
- * every call and log a warning about it.
6
+ * *required*, and a sessioned function would demand one regardless of the flag.
7
+ * `scaffold.scenarios: true` therefore requires a session; `{ auth: false }` is
8
+ * what opens them.
9
9
  */
10
10
  export declare const registerScenarioInstrumentation: (requireAuth: boolean) => void;
@@ -79,9 +79,9 @@ const instrumentation = {
79
79
  * scenario runner can reach it over `/rpc/<name>` exactly as before.
80
80
  *
81
81
  * They are registered sessionless: `auth` decides whether a session is
82
- * *required*, and a sessioned function would demand one regardless of the flag
83
- * which is what made `scaffold.scenarios: true` (as opposed to `'auth'`) refuse
84
- * every call and log a warning about it.
82
+ * *required*, and a sessioned function would demand one regardless of the flag.
83
+ * `scaffold.scenarios: true` therefore requires a session; `{ auth: false }` is
84
+ * what opens them.
85
85
  */
86
86
  export const registerScenarioInstrumentation = (requireAuth) => {
87
87
  const meta = pikkuState(null, 'function', 'meta');
@@ -3,7 +3,7 @@ export interface SerializeScopesOptions {
3
3
  definitions: ScopeDefinitions;
4
4
  }
5
5
  /**
6
- * Generates the `ScopeId` union and scope metadata from `wireScope`
6
+ * Generates the `ScopeId` union and scope metadata from `defineScope`
7
7
  * declarations.
8
8
  *
9
9
  * The union carries every declared node plus a wildcard form for each node that
@@ -1,7 +1,7 @@
1
1
  import { flattenScopeDefinitions } from '@pikku/core/scope';
2
2
  const quote = (value) => `'${value.replace(/'/g, "\\'")}'`;
3
3
  /**
4
- * Generates the `ScopeId` union and scope metadata from `wireScope`
4
+ * Generates the `ScopeId` union and scope metadata from `defineScope`
5
5
  * declarations.
6
6
  *
7
7
  * The union carries every declared node plus a wildcard form for each node that
@@ -3,6 +3,7 @@ import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
3
3
  import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
5
  import { serializeUserAdminFunctions } from './serialize-user-admin-functions.js';
6
+ import { resolveScaffoldFeature } from '../../../utils/resolve-scaffold-feature.js';
6
7
  export const pikkuUserAdminFunctions = pikkuSessionlessFunc({
7
8
  func: async ({ logger, config, getInspectorState }) => {
8
9
  if (!config.scaffold?.userAdmin ||
@@ -35,7 +36,7 @@ export const pikkuUserAdminFunctions = pikkuSessionlessFunc({
35
36
  `Then re-run codegen. Or remove "scaffold.userAdmin" from pikku.config.json.`);
36
37
  }
37
38
  const pathToPikkuTypes = getFileImportRelativePath(config.userAdminFunctionsFile, config.typesDeclarationFile, config.packageMappings);
38
- const { schemas, functions } = serializeUserAdminFunctions(pathToPikkuTypes, config.scaffold.userAdmin === 'auth');
39
+ const { schemas, functions } = serializeUserAdminFunctions(pathToPikkuTypes, resolveScaffoldFeature('userAdmin', config.scaffold.userAdmin).auth);
39
40
  await writeFileInDir(logger, config.userAdminSchemasFile, schemas);
40
41
  await writeFileInDir(logger, config.userAdminFunctionsFile, functions);
41
42
  return true;
@@ -86,7 +86,7 @@ export const Success = z.object({
86
86
  * Auto-generated user management functions
87
87
  * Do not edit manually - regenerate with 'npx pikku'
88
88
  */
89
- import { pikkuFunc, wireScope } from '${pathToPikkuTypes}'
89
+ import { pikkuFunc, defineScope } from '${pathToPikkuTypes}'
90
90
  import { callAdminApi } from '@pikku/better-auth'
91
91
  import {
92
92
  CreateUserInput,
@@ -103,7 +103,7 @@ import {
103
103
  // this is the whole \`admin\` tree — not just the leaves gated below. It must stay
104
104
  // byte-identical to ADMIN_SCOPE_TREE in @pikku/better-auth and to the copy in
105
105
  // @pikku/addon-console, or codegen fails with conflicting declarations.
106
- wireScope({
106
+ defineScope({
107
107
  admin: {
108
108
  displayName: 'Administration',
109
109
  description: 'Capabilities that act on the application as a whole',
@@ -4,13 +4,14 @@ import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
5
  import { removeLegacyScaffoldFile } from '../../../utils/remove-legacy-scaffold-file.js';
6
6
  import { serializeWorkflowRoutes } from './serialize-workflow-routes.js';
7
+ import { resolveScaffoldFeature } from '../../../utils/resolve-scaffold-feature.js';
7
8
  export const pikkuWorkflowRoutes = pikkuSessionlessFunc({
8
9
  func: async ({ logger, config }) => {
9
10
  if (config.scaffold?.workflow &&
10
11
  config.workflowRoutesFile &&
11
12
  config.workflowRoutesSchemasFile) {
12
13
  const pathToPikkuTypes = getFileImportRelativePath(config.workflowRoutesFile, config.typesDeclarationFile, config.packageMappings);
13
- const { schemas, functions } = serializeWorkflowRoutes(pathToPikkuTypes, config.scaffold?.workflow === 'auth');
14
+ const { schemas, functions } = serializeWorkflowRoutes(pathToPikkuTypes, resolveScaffoldFeature('workflow', config.scaffold?.workflow).auth);
14
15
  await writeFileInDir(logger, config.workflowRoutesSchemasFile, schemas);
15
16
  await writeFileInDir(logger, config.workflowRoutesFile, functions);
16
17
  await removeLegacyScaffoldFile(config.workflowRoutesFile);
@@ -3,8 +3,8 @@ import { ErrorCode } from '@pikku/inspector';
3
3
  import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
5
  import { serializeWorkflowTypes } from './serialize-workflow-types.js';
6
- import { serializeScenarioActors } from './serialize-scenario-actors.js';
7
- import { resolveScenarioActors } from '../../../utils/resolve-scenario-actors.js';
6
+ import { serializePersonas } from './serialize-personas.js';
7
+ import { resolvePersonas } from '../../../utils/resolve-personas.js';
8
8
  import { serializeWorkflowRegistration } from './serialize-workflow-registration.js';
9
9
  import { serializeWorkflowMap } from './serialize-workflow-map.js';
10
10
  import { serializeScenarioStepMap } from './serialize-scenario-step-map.js';
@@ -89,21 +89,21 @@ export const pikkuWorkflow = pikkuSessionlessFunc({
89
89
  const agentMapImportPath = getFileImportRelativePath(workflowTypesFile, config.agentMapDeclarationFile, packageMappings);
90
90
  const scopesImportPath = getFileImportRelativePath(workflowTypesFile, config.scopesFile, packageMappings);
91
91
  const scenarioStepMapImportPath = getFileImportRelativePath(workflowTypesFile, config.scenarioStepMapDeclarationFile, packageMappings);
92
- const scenarioActorsImportPath = getFileImportRelativePath(workflowTypesFile, config.scenarioActorsFile, packageMappings);
93
- await writeFileInDir(logger, workflowTypesFile, serializeWorkflowTypes(functionTypesImportPath, rpcMapImportPath, workflowMapImportPath, agentMapImportPath, scopesImportPath, scenarioStepMapImportPath, scenarioActorsImportPath));
92
+ const personasImportPath = getFileImportRelativePath(workflowTypesFile, config.personasWiringFile, packageMappings);
93
+ await writeFileInDir(logger, workflowTypesFile, serializeWorkflowTypes(functionTypesImportPath, rpcMapImportPath, workflowMapImportPath, agentMapImportPath, scopesImportPath, scenarioStepMapImportPath, personasImportPath));
94
94
  // Rides the workflow command rather than getting its own codegen node, so
95
95
  // it can't drift out of the two hand-maintained ordering lists.
96
96
  await writeFileInDir(logger, config.scenarioStepMapDeclarationFile, serializeScenarioStepMap(logger, config.scenarioStepMapDeclarationFile, packageMappings, typesMap, functionState.meta));
97
- // Written even for an empty registry, so `TypedScenarioActors` is always a
98
- // resolvable import for the generated function types.
99
- const scenarioActors = resolveScenarioActors(config.scenarios);
97
+ // Written even when nobody declared a persona, so `TypedPersonas` is always
98
+ // a resolvable import for the generated function types.
99
+ const personas = resolvePersonas(visitState.personas.definitions, config.scenarios?.emailDomain);
100
100
  {
101
- const agentMapImportPath = getFileImportRelativePath(config.scenarioActorsFile, config.agentMapDeclarationFile, packageMappings);
102
- const exposedRpcMapImportPath = getFileImportRelativePath(config.scenarioActorsFile, config.rpcMapDeclarationFile, packageMappings);
103
- await writeFileInDir(logger, config.scenarioActorsFile, serializeScenarioActors(scenarioActors, agentMapImportPath, exposedRpcMapImportPath));
104
- // Fixed path getScenarioActorsMeta() reads; kept out of workflow/meta,
105
- // which getWorkflowMeta() globs as workflows.
106
- await writeFileInDir(logger, join(config.outDir, 'workflow', 'scenario-actors.gen.json'), JSON.stringify(scenarioActors, null, 2));
101
+ const agentMapImportPath = getFileImportRelativePath(config.personasWiringFile, config.agentMapDeclarationFile, packageMappings);
102
+ const exposedRpcMapImportPath = getFileImportRelativePath(config.personasWiringFile, config.rpcMapDeclarationFile, packageMappings);
103
+ await writeFileInDir(logger, config.personasWiringFile, serializePersonas(personas, agentMapImportPath, exposedRpcMapImportPath));
104
+ // Fixed path getPersonasMeta() reads; kept out of workflow/meta, which
105
+ // getWorkflowMeta() globs as workflows.
106
+ await writeFileInDir(logger, join(config.outDir, 'workflow', 'personas.gen.json'), JSON.stringify(personas, null, 2));
107
107
  }
108
108
  // Fixed path getFeaturesMeta() reads. Written even when empty so the
109
109
  // console can tell "no features declared" from "meta not generated yet".
@@ -0,0 +1,10 @@
1
+ import type { ResolvedPersona } from '@pikku/core/services';
2
+ /**
3
+ * The runtime half of `definePersonas()`.
4
+ *
5
+ * Generated rather than re-exported because the addresses are computed: nobody
6
+ * writes one down, and a seed, a scenario run and a virtual-user run all have
7
+ * to arrive at the same one. Baking them here is what makes that a single fact
8
+ * rather than three implementations of the same rule.
9
+ */
10
+ export declare const serializePersonas: (personas: Record<string, ResolvedPersona>, agentMapImportPath: string, rpcMapImportPath: string) => string;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The runtime half of `definePersonas()`.
3
+ *
4
+ * Generated rather than re-exported because the addresses are computed: nobody
5
+ * writes one down, and a seed, a scenario run and a virtual-user run all have
6
+ * to arrive at the same one. Baking them here is what makes that a single fact
7
+ * rather than three implementations of the same rule.
8
+ */
9
+ export const serializePersonas = (personas, agentMapImportPath, rpcMapImportPath) => {
10
+ return `/** Personas declared with definePersonas() */
11
+ import { createHttpPersonas } from '@pikku/core/persona'
12
+ import type {
13
+ HttpPersonasConfig,
14
+ ScenarioPersona,
15
+ ResolvedPersona,
16
+ } from '@pikku/core/services'
17
+ import type { AgentMap } from '${agentMapImportPath}'
18
+ import type { FlattenedRPCMap } from '${rpcMapImportPath}'
19
+
20
+ export const personaConfigs = ${JSON.stringify(personas, null, 2)} as const satisfies Record<string, ResolvedPersona>
21
+
22
+ export type PersonaName = keyof typeof personaConfigs
23
+
24
+ /**
25
+ * The same personas as a list, widened to \`ResolvedPersona\`. Iterating
26
+ * \`personaConfigs\` directly yields the literal type of each entry, on which an
27
+ * optional field a persona did not declare does not exist — so a seed reading
28
+ * \`roles\` off every persona needs this instead.
29
+ */
30
+ export const personaList: ResolvedPersona[] = Object.values(personaConfigs)
31
+
32
+ export type AgentName = keyof AgentMap & string
33
+ export type TypedPersonas = Record<
34
+ PersonaName,
35
+ ScenarioPersona<AgentName, FlattenedRPCMap>
36
+ >
37
+
38
+ export const createPersonas = (
39
+ options: Omit<HttpPersonasConfig, 'personas'>
40
+ ): TypedPersonas =>
41
+ createHttpPersonas({
42
+ ...options,
43
+ personas: personaConfigs,
44
+ }) as TypedPersonas
45
+ `;
46
+ };
@@ -9,10 +9,19 @@
9
9
  */
10
10
  import { serializeImportMap } from '../../../utils/serialize-import-map.js';
11
11
  import { generateCustomTypes } from '@pikku/inspector';
12
- const SCENARIO_STEP_WRAPPER = 'pikkuScenarioStep';
12
+ /**
13
+ * All three step wrappers. A scenario calls a platform or addon step by name
14
+ * exactly as it calls a persona one — who acts changes what the step may do, not
15
+ * how it is referenced — so all three belong in the map.
16
+ */
17
+ const SCENARIO_STEP_WRAPPERS = new Set([
18
+ 'pikkuScenarioStep',
19
+ 'pikkuPlatformScenarioStep',
20
+ 'pikkuAddonScenarioStep',
21
+ ]);
13
22
  export const serializeScenarioStepMap = (logger, relativeToPath, packageMappings, typesMap, functionsMeta) => {
14
23
  const requiredTypes = new Set();
15
- const steps = Object.entries(functionsMeta).filter(([, meta]) => meta.funcWrapper === SCENARIO_STEP_WRAPPER);
24
+ const steps = Object.entries(functionsMeta).filter(([, meta]) => !!meta.funcWrapper && SCENARIO_STEP_WRAPPERS.has(meta.funcWrapper));
16
25
  const resolveType = (name) => {
17
26
  if (!name) {
18
27
  return 'void';
@@ -1,4 +1,4 @@
1
- export const serializeWorkflowTypes = (functionTypesImportPath, rpcMapImportPath, workflowMapImportPath, agentMapImportPath, scopesImportPath, scenarioStepMapImportPath = './pikku-scenario-step-map.gen.js', scenarioActorsImportPath = './pikku-scenario-actors.gen.js') => {
1
+ export const serializeWorkflowTypes = (functionTypesImportPath, rpcMapImportPath, workflowMapImportPath, agentMapImportPath, scopesImportPath, scenarioStepMapImportPath = './pikku-scenario-step-map.gen.js', scenarioActorsImportPath = './pikku-personas.gen.js') => {
2
2
  return `import { WorkflowCancelledException } from '@pikku/core/workflow'
3
3
  import { template } from '@pikku/core/workflow'
4
4
  import {
@@ -14,9 +14,9 @@ import type { FlattenedRPCMap } from '${rpcMapImportPath}'
14
14
  import type { FlattenedWorkflowMap } from '${workflowMapImportPath}'
15
15
  import type { AgentMap as FlattenedAgentMap } from '${agentMapImportPath}'
16
16
  import type { FlattenedScenarioStepMap } from '${scenarioStepMapImportPath}'
17
- import type { TypedScenarioActors } from '${scenarioActorsImportPath}'
17
+ import type { TypedPersonas } from '${scenarioActorsImportPath}'
18
18
 
19
- export type { TypedScenarioActors }
19
+ export type { TypedPersonas }
20
20
 
21
21
  export { template }
22
22
 
@@ -308,16 +308,26 @@ export function pikkuFeature<const Scenarios extends readonly unknown[]>(
308
308
  return config
309
309
  }
310
310
 
311
+ /**
312
+ * One surface's implementation of a step.
313
+ *
314
+ * Each binding is typed independently, so a browser binding gets a non-optional
315
+ * \`wire.browser\` and a cli binding a non-optional \`wire.cli\` without either
316
+ * leaking into the other.
317
+ */
311
318
  export type PikkuFunctionScenarioStep<
312
319
  In = unknown,
313
320
  Out = never,
314
- B extends boolean = false
315
- > = PikkuFunctionSessionless<In, Out, B extends true ? 'scenarioStep' | 'browser' : 'scenarioStep'>
321
+ Surface extends 'browser' | 'cli' | 'default' = 'default'
322
+ > = PikkuFunctionSessionless<
323
+ In,
324
+ Out,
325
+ Surface extends 'default' ? 'scenarioStep' : 'scenarioStep' | Surface
326
+ >
316
327
 
317
328
  export type PikkuScenarioStepConfigWithSchema<
318
329
  InputSchema extends StandardSchemaV1 | undefined = undefined,
319
- OutputSchema extends StandardSchemaV1 | undefined = undefined,
320
- B extends boolean = false
330
+ OutputSchema extends StandardSchemaV1 | undefined = undefined
321
331
  > = {
322
332
  /** Registered name — this is the string \`scenario.step()\` references. */
323
333
  name: string
@@ -336,49 +346,175 @@ export type PikkuScenarioStepConfigWithSchema<
336
346
  * sentence per call site.
337
347
  */
338
348
  template?: string
339
- /**
340
- * This step drives a browser. The runner provisions one for the step's actor
341
- * before calling it — which also makes an actor mandatory — and \`wire.browser\`
342
- * is non-optional inside \`func\`.
343
- */
344
- browser?: B
345
349
  title?: string
346
350
  tags?: string[]
347
351
  input?: InputSchema
348
352
  output?: OutputSchema
349
353
  errors?: Array<typeof PikkuError>
350
- func: PikkuFunctionScenarioStep<
354
+ /**
355
+ * Drive this step through a real browser, as a human would. The runner
356
+ * provisions a window for the step's actor first — which makes an actor
357
+ * mandatory — and \`wire.browser\` is non-optional inside it.
358
+ *
359
+ * Compose shared utilities here rather than writing raw clicks: the step says
360
+ * what the actor is doing, the utilities say how.
361
+ */
362
+ browser?: PikkuFunctionScenarioStep<
363
+ InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown,
364
+ OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
365
+ 'browser'
366
+ >
367
+ /** Drive this step remotely, over the websocket. */
368
+ cli?: PikkuFunctionScenarioStep<
351
369
  InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown,
352
370
  OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
353
- B
371
+ 'cli'
372
+ >
373
+ /**
374
+ * The server-side path — the fast suite, and the floor every other surface
375
+ * falls back to.
376
+ *
377
+ * On a \`then\`, this is not an alternative to the other bindings but a second
378
+ * **witness**: the run executes every binding it has and fails if they
379
+ * disagree, because "the row says paid" and "the page says paid" are different
380
+ * claims and the gap between them is the bug nobody catches.
381
+ */
382
+ default?: PikkuFunctionScenarioStep<
383
+ InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown,
384
+ OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
385
+ 'default'
354
386
  >
355
387
  }
356
388
 
357
- export type PikkuScenarioStepConfig<In, Out, B extends boolean = false> =
358
- Omit<PikkuScenarioStepConfigWithSchema<undefined, undefined, B>, 'func' | 'input' | 'output'> & {
359
- func: PikkuFunctionScenarioStep<In, Out, B>
389
+ export type PikkuScenarioStepConfig<In, Out> =
390
+ Omit<PikkuScenarioStepConfigWithSchema<undefined, undefined>, 'browser' | 'cli' | 'default' | 'input' | 'output'> & {
391
+ browser?: PikkuFunctionScenarioStep<In, Out, 'browser'>
392
+ cli?: PikkuFunctionScenarioStep<In, Out, 'cli'>
393
+ default?: PikkuFunctionScenarioStep<In, Out, 'default'>
360
394
  }
361
395
 
362
396
  /**
363
- * A named, reusable scenario step. Unlike \`scenario.do\`, which can only name an
364
- * RPC, a step's body is an ordinary pikku function — so it may drive a browser,
365
- * invoke RPCs as its actor, or run a workflow.
397
+ * A named, reusable scenario step, declaring one implementation per surface an
398
+ * actor can drive it through.
399
+ *
400
+ * The step's identity is what the actor is trying to do; which surface carries
401
+ * it out is a binding. That is what lets one ladder run through a real browser,
402
+ * over the websocket, or entirely server-side — and what makes "how much of this
403
+ * flow can a human actually reach" a number rather than a guess.
366
404
  *
367
405
  * Steps are deliberately NOT registered as RPCs: a browser-driving step must
368
406
  * never be network-callable.
369
407
  */
370
408
  export function pikkuScenarioStep<
371
409
  InputSchema extends StandardSchemaV1 | undefined = undefined,
372
- OutputSchema extends StandardSchemaV1 | undefined = undefined,
373
- B extends boolean = false
410
+ OutputSchema extends StandardSchemaV1 | undefined = undefined
374
411
  >(
375
- config: PikkuScenarioStepConfigWithSchema<InputSchema, OutputSchema, B>
376
- ): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, B extends true ? 'scenarioStep' | 'browser' : 'scenarioStep', PikkuFunctionScenarioStep<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, B>, InputSchema, OutputSchema>
377
- export function pikkuScenarioStep<In, Out = unknown, B extends boolean = false>(
378
- config: PikkuScenarioStepConfig<In, Out, B>
379
- ): PikkuFunctionConfig<In, Out, B extends true ? 'scenarioStep' | 'browser' : 'scenarioStep'>
412
+ config: PikkuScenarioStepConfigWithSchema<InputSchema, OutputSchema>
413
+ ): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'scenarioStep' | 'browser' | 'cli', PikkuFunctionScenarioStep<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'browser' | 'cli' | 'default'>, InputSchema, OutputSchema>
414
+ export function pikkuScenarioStep<In, Out = unknown>(
415
+ config: PikkuScenarioStepConfig<In, Out>
416
+ ): PikkuFunctionConfig<In, Out, 'scenarioStep' | 'browser' | 'cli'>
380
417
  export function pikkuScenarioStep(config: any) {
381
- return config
418
+ const surfaces = (['browser', 'cli', 'default'] as const).filter(
419
+ (surface) => typeof config[surface] === 'function'
420
+ )
421
+ if (surfaces.length === 0) {
422
+ throw new Error(
423
+ \`[scenario] step '\${config.name}' declares no surface bindings. Add at least a \\\`default\\\` (server-side) implementation.\`
424
+ )
425
+ }
426
+ return {
427
+ ...config,
428
+ surfaces,
429
+ // The runner sets the surface on the wire and calls this once per binding it
430
+ // resolved — every witness of a \`then\`, exactly one for an action step.
431
+ func: (services: any, data: any, wire: any) => {
432
+ const surface = wire?.scenarioStep?.surface ?? 'default'
433
+ const binding = config[surface] ?? config.default
434
+ return binding(services, data, wire)
435
+ },
436
+ }
437
+ }
438
+
439
+ /**
440
+ * What a step declares when nobody drives it: one implementation, and no
441
+ * surfaces to choose between.
442
+ *
443
+ * \`func\` rather than \`default\` is the point. \`default\` means *the fallback when
444
+ * no other surface applies*, which implies others could exist; \`func\` says
445
+ * structurally that there is one way this happens. That also keeps the phase
446
+ * rule coherent — an assertion runs every witness it has and fails if they
447
+ * disagree, and this has exactly one by construction.
448
+ */
449
+ export type PikkuSubjectScenarioStepConfigWithSchema<
450
+ InputSchema extends StandardSchemaV1 | undefined = undefined,
451
+ OutputSchema extends StandardSchemaV1 | undefined = undefined
452
+ > = Omit<
453
+ PikkuScenarioStepConfigWithSchema<InputSchema, OutputSchema>,
454
+ 'browser' | 'cli' | 'default'
455
+ > & {
456
+ func: PikkuFunctionScenarioStep<
457
+ InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown,
458
+ OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
459
+ 'default'
460
+ >
461
+ }
462
+
463
+ export type PikkuSubjectScenarioStepConfig<In, Out> =
464
+ Omit<PikkuScenarioStepConfig<In, Out>, 'browser' | 'cli' | 'default'> & {
465
+ func: PikkuFunctionScenarioStep<In, Out, 'default'>
466
+ }
467
+
468
+ /**
469
+ * A step in which the app acts on itself — "Given the platform has expired the
470
+ * trial".
471
+ *
472
+ * The grammatical subject of that sentence is not a user of your app; it **is**
473
+ * your app, which is why it is its own declaration rather than a persona with an
474
+ * asterisk. A persona is a person.
475
+ *
476
+ * Local-test-only, and never in a virtual user's catalogue: a virtual user that
477
+ * could expire its own trial is manufacturing the outcome it exists to discover.
478
+ */
479
+ export function pikkuPlatformScenarioStep<
480
+ InputSchema extends StandardSchemaV1 | undefined = undefined,
481
+ OutputSchema extends StandardSchemaV1 | undefined = undefined
482
+ >(
483
+ config: PikkuSubjectScenarioStepConfigWithSchema<InputSchema, OutputSchema>
484
+ ): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'scenarioStep', PikkuFunctionScenarioStep<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'default'>, InputSchema, OutputSchema>
485
+ export function pikkuPlatformScenarioStep<In, Out = unknown>(
486
+ config: PikkuSubjectScenarioStepConfig<In, Out>
487
+ ): PikkuFunctionConfig<In, Out, 'scenarioStep'>
488
+ export function pikkuPlatformScenarioStep(config: any) {
489
+ return { ...config, surfaces: ['default'] }
490
+ }
491
+
492
+ /**
493
+ * A step in which a third-party system acts — "Given Stripe's webhook arrives",
494
+ * "When Mailgun bounces it".
495
+ *
496
+ * \`addon\` names the addon that wraps that service, the same name its
497
+ * \`wireAddon\` declares. These steps *are* the mock its consumers currently
498
+ * hand-write: shipped by the addon author, maintained with the addon, and the
499
+ * same artifact that appears in the prose.
500
+ *
501
+ * Note that arranging and asserting are different — "Stripe's webhook arrives"
502
+ * stubs, "Then Stripe was charged" asserts, and only the first is a stub.
503
+ *
504
+ * Local-test-only, and never in a virtual user's catalogue: one that could
505
+ * invoke this would forge its own payment success.
506
+ */
507
+ export function pikkuAddonScenarioStep<
508
+ InputSchema extends StandardSchemaV1 | undefined = undefined,
509
+ OutputSchema extends StandardSchemaV1 | undefined = undefined
510
+ >(
511
+ config: PikkuSubjectScenarioStepConfigWithSchema<InputSchema, OutputSchema> & { addon: string }
512
+ ): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'scenarioStep', PikkuFunctionScenarioStep<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'default'>, InputSchema, OutputSchema>
513
+ export function pikkuAddonScenarioStep<In, Out = unknown>(
514
+ config: PikkuSubjectScenarioStepConfig<In, Out> & { addon: string }
515
+ ): PikkuFunctionConfig<In, Out, 'scenarioStep'>
516
+ export function pikkuAddonScenarioStep(config: any) {
517
+ return { ...config, surfaces: ['default'] }
382
518
  }
383
519
 
384
520
  type TypedRef<T> = { $ref: string; path?: string } & { __phantomType?: T }
@@ -53,6 +53,10 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
53
53
  // schemas, for one — fails until these two files exist.
54
54
  await workflow.do('Bootstrap scope definition types', 'pikkuScopeDefinitionTypes', null);
55
55
  await workflow.do('Bootstrap scopes', 'pikkuScopes', { bootstrap: true });
56
+ await workflow.do('Bootstrap roles', 'pikkuRoles', { bootstrap: true });
57
+ await workflow.do('Bootstrap personas', 'pikkuPersonas', {
58
+ bootstrap: true,
59
+ });
56
60
  await workflow.do('Bootstrap function types split', 'pikkuFunctionTypesSplit', { bootstrap: true });
57
61
  // Pre-write a stub auth.types.ts (if the project uses better-auth) so the
58
62
  // pikkuBetterAuth re-export resolves before any user file is imported.
@@ -155,6 +159,8 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
155
159
  workflow.do('Credentials', 'pikkuCredentials', null),
156
160
  workflow.do('Scope definition types', 'pikkuScopeDefinitionTypes', null),
157
161
  workflow.do('Scopes', 'pikkuScopes', {}),
162
+ workflow.do('Roles', 'pikkuRoles', {}),
163
+ workflow.do('Personas', 'pikkuPersonas', {}),
158
164
  workflow.do('Variable definition types', 'pikkuVariableDefinitionTypes', null),
159
165
  workflow.do('Variables', 'pikkuVariables', null),
160
166
  workflow.do('Addon types', 'pikkuAddonTypes', null),
@@ -165,7 +171,7 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
165
171
  if (agents || !config.addon) {
166
172
  await workflow.do('Re-inspect after agents', async () => getInspectorState(true));
167
173
  }
168
- // pikkuAuth generates auth-secrets.gen.ts (with wireSecret calls) inside the
174
+ // pikkuAuth generates auth-secrets.gen.ts (with defineSecret calls) inside the
169
175
  // Promise.all above, but pikkuSecrets runs concurrently from the pre-auth
170
176
  // inspector state. Re-run secret/credential/variable codegen now so that the
171
177
  // freshly-generated auth-secrets.gen.ts (just picked up by Re-inspect above)