@pikku/cli 0.12.96 → 0.12.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/console-app/assets/abnfDiagram-VRR7QNED-CytV-CG1.js +1 -0
- package/console-app/assets/arc-C5PvmnaA.js +1 -0
- package/console-app/assets/architecture-TIHT7OUA-C1_ScvPX.js +1 -0
- package/console-app/assets/architectureDiagram-ZJ3FMSHR-CJ2xb7C-.js +36 -0
- package/console-app/assets/array-BifhSqXX.js +1 -0
- package/console-app/assets/blockDiagram-677ZJIJ3-CCecrBrk.js +132 -0
- package/console-app/assets/c4Diagram-LMCZKHZV-CBnpukvQ.js +10 -0
- package/console-app/assets/channel-CwDOkXGI.js +1 -0
- package/console-app/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/console-app/assets/chunk-32BRIVSS-D8qWWZRQ.js +1 -0
- package/console-app/assets/chunk-52WLFC77-BEpXHMsp.js +10 -0
- package/console-app/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/console-app/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- package/console-app/assets/chunk-C7G6YPKG-DsHUMkOp.js +1 -0
- package/console-app/assets/chunk-EX3LRPZG-mM7TjVx9.js +231 -0
- package/console-app/assets/chunk-FWX5IMBZ-CndRAn18.js +2 -0
- package/console-app/assets/chunk-HOUHSVGY-D1kT8umn.js +1 -0
- package/console-app/assets/chunk-ICXQ74PX-CeSaGyro.js +2 -0
- package/console-app/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/console-app/assets/chunk-KEIR6QF5-DNzq6p3w.js +161 -0
- package/console-app/assets/chunk-MOJQB5TN-CaZOCHYr.js +88 -0
- package/console-app/assets/chunk-OGEWGWER-CnB1CsPt.js +1 -0
- package/console-app/assets/chunk-PUDLZKDR-BbGulHJm.js +156 -0
- package/console-app/assets/chunk-Q4XR5HBZ-BywwzeeR.js +70 -0
- package/console-app/assets/chunk-RYQCIY6F-CV0R1Adv.js +1 -0
- package/console-app/assets/chunk-V7JOEXUC-DGFaAWzB.js +206 -0
- package/console-app/assets/chunk-VAUOI2AC-DxPUd8Yb.js +1 -0
- package/console-app/assets/chunk-VR4S4FIN-D4-UaW_J.js +1 -0
- package/console-app/assets/chunk-WYO6CB5R-D_TFN0d9.js +127 -0
- package/console-app/assets/chunk-XXDRQBXY-BzVuweA_.js +1 -0
- package/console-app/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/console-app/assets/chunk-ZGVPDNZ5-C6hdkGQb.js +62 -0
- package/console-app/assets/chunk-ZIRB5QZD-H-kepumB.js +32 -0
- package/console-app/assets/classDiagram-OUVF2IWQ-CTYJkTT0.js +1 -0
- package/console-app/assets/classDiagram-v2-EOCWNBFH-CTYJkTT0.js +1 -0
- package/console-app/assets/cose-bilkent-JH36ORCC-B6ZPxN6v.js +1 -0
- package/console-app/assets/cynefin-VYW2F7L2-D_nmxNz_.js +1 -0
- package/console-app/assets/cynefinDiagram-TSTJHNR4-CHy5QN0S.js +62 -0
- package/console-app/assets/cytoscape.esm-h6BdjjI9.js +321 -0
- package/console-app/assets/dagre-CXRCoUWR.js +1 -0
- package/console-app/assets/dagre-VKFMJZFB-DYfukM34.js +4 -0
- package/console-app/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/console-app/assets/diagram-FQU43EPY-Dgb9TPSB.js +3 -0
- package/console-app/assets/diagram-G47NLZAW-CnFNZs9Z.js +24 -0
- package/console-app/assets/diagram-NH7WQ7WH-DN7h2DU-.js +24 -0
- package/console-app/assets/diagram-OA4YK3LP-RLaQaNtK.js +30 -0
- package/console-app/assets/diagram-WEI45ONY-DxHWhVGd.js +41 -0
- package/console-app/assets/dist-BaJNIo4I.js +1 -0
- package/console-app/assets/ebnfDiagram-CCIWWBDH-UN3Z81-l.js +1 -0
- package/console-app/assets/erDiagram-Q63AITRT-C9lGAY3i.js +85 -0
- package/console-app/assets/eventmodeling-45OFAUF4-Dc5wNSSK.js +1 -0
- package/console-app/assets/flowDiagram-23GEKE2U-zPyGHMi4.js +1 -0
- package/console-app/assets/ganttDiagram-NO4QXBWP-CvBjBcdK.js +292 -0
- package/console-app/assets/gitGraph-TEB2WS4Q-Cs5Z9jMo.js +1 -0
- package/console-app/assets/gitGraphDiagram-IHSO6WYX-B0i_IJ5r.js +106 -0
- package/console-app/assets/graphlib-B8gBHxth.js +1 -0
- package/console-app/assets/{index-CeQodYfg.js → index-71U8EEj7.js} +155 -153
- package/console-app/assets/{index-CyYHTGWZ.css → index-BXMjqLQ5.css} +1 -1
- package/console-app/assets/info-DKCQHKI2-DHL0afNl.js +1 -0
- package/console-app/assets/infoDiagram-FWYZ7A6U-CBWykodX.js +2 -0
- package/console-app/assets/init-D6jRqBbL.js +1 -0
- package/console-app/assets/ishikawaDiagram-FXEZZL3T-DM377eW_.js +70 -0
- package/console-app/assets/journeyDiagram-5HDEW3XC-BpX_-Cns.js +139 -0
- package/console-app/assets/kanban-definition-HUTT4EX6-D3xFsQkO.js +89 -0
- package/console-app/assets/katex-Vhh-h91d.js +257 -0
- package/console-app/assets/line-CTSKHTE0.js +1 -0
- package/console-app/assets/linear-R5GlBvXT.js +1 -0
- package/console-app/assets/map-DsCK-0Cs.js +1 -0
- package/console-app/assets/mermaid-parser.core-CkHk4BPi.js +7 -0
- package/console-app/assets/mermaid.core-CcjGXuV6.js +11 -0
- package/console-app/assets/mindmap-definition-LN4V7U3C-CtolazqQ.js +96 -0
- package/console-app/assets/ordinal-hYBb2elL.js +1 -0
- package/console-app/assets/packet-7NZHBO7P-BxSOj_Ny.js +1 -0
- package/console-app/assets/path-BWPyau1x.js +1 -0
- package/console-app/assets/pegDiagram-2B236MQR-BNsMtxsM.js +1 -0
- package/console-app/assets/pie-RZYD4A2V-BbkKQBA5.js +1 -0
- package/console-app/assets/pieDiagram-ENE6RG2P-BAZLIz2f.js +39 -0
- package/console-app/assets/quadrantDiagram-ABIIQ3AL-Dpvn-1Fs.js +7 -0
- package/console-app/assets/radar-I7S5WNFK-B-uKXIAH.js +1 -0
- package/console-app/assets/railroad-3IZDKUUU-BHXXK04t.js +1 -0
- package/console-app/assets/railroad-abnf-AHOZXSZD-tXSQaBvY.js +1 -0
- package/console-app/assets/railroad-ebnf-EBAXGLYW-DG3aohR0.js +1 -0
- package/console-app/assets/railroad-peg-LSFZ7HO6-DY_gBA-x.js +1 -0
- package/console-app/assets/railroadDiagram-RFXS5EU6-DfUApEQj.js +1 -0
- package/console-app/assets/requirementDiagram-TGXJPOKE-B2zX0jqo.js +84 -0
- package/console-app/assets/rough.esm-CSKSodPl.js +1 -0
- package/console-app/assets/sankeyDiagram-HTMAVEWB-CbQ2zc7z.js +40 -0
- package/console-app/assets/sequenceDiagram-DBY2YBRQ-BLT8PXI_.js +162 -0
- package/console-app/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- package/console-app/assets/src-BLuPUFWW.js +1 -0
- package/console-app/assets/stateDiagram-2N3HPSRC-BvEyVp85.js +1 -0
- package/console-app/assets/stateDiagram-v2-6OUMAXLB-RGKS9iU4.js +1 -0
- package/console-app/assets/swimlanes-5IMT3BWC-1AZ459s6.js +2 -0
- package/console-app/assets/swimlanesDiagram-G3AALYLV-Ck_0-rfN.js +8 -0
- package/console-app/assets/timeline-definition-FHXFAJF6-dLuvH4s2.js +120 -0
- package/console-app/assets/treeView-QDETBFTQ-xL2msjo2.js +1 -0
- package/console-app/assets/treemap-6X3UGDF4-C98ichVP.js +1 -0
- package/console-app/assets/vennDiagram-L72KCM5P-HbUmidhv.js +34 -0
- package/console-app/assets/wardley-OPB4EBWU-C4gJS-m2.js +1 -0
- package/console-app/assets/wardleyDiagram-EHGQE667-B7b-LB02.js +78 -0
- package/console-app/assets/xychartDiagram-FW5EYKEG-Cm03zUGy.js +7 -0
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/auth/auth.types.d.ts +1 -1
- package/dist/.pikku/auth/auth.types.js +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +1 -6
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +36 -9
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +7 -8
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +18 -2
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +12 -6
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +39 -30
- package/dist/.pikku/function/pikku-functions.gen.js +3 -3
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +2 -1
- package/dist/.pikku/pikku-services.gen.js +1 -0
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.js +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +3 -3
- package/dist/.pikku/schemas/schemas/DbResetInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/{DbSeedInput.schema.json → PikkuVirtualUserFunctionsOutput.schema.json} +1 -1
- package/dist/.pikku/scopes/pikku-personas.gen.js +1 -1
- package/dist/.pikku/scopes/pikku-roles.gen.d.ts +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +20 -14
- package/dist/.pikku/workflow/pikku-personas.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +6 -6
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +8 -7
- package/dist/src/fabric/functions/validate.function.js +76 -176
- package/dist/src/functions/commands/all.d.ts +1 -1
- package/dist/src/functions/commands/bootstrap.d.ts +1 -1
- package/dist/src/functions/commands/db-audit.js +1 -1
- package/dist/src/functions/commands/db-baseline.js +1 -1
- package/dist/src/functions/commands/db-check.js +6 -5
- package/dist/src/functions/commands/db-codegen.js +1 -1
- package/dist/src/functions/commands/db-generate.js +1 -1
- package/dist/src/functions/commands/db-migrate.js +1 -1
- package/dist/src/functions/commands/db-reset.d.ts +15 -1
- package/dist/src/functions/commands/db-reset.js +18 -6
- package/dist/src/functions/commands/db-shared.d.ts +14 -0
- package/dist/src/functions/commands/deploy-info.d.ts +1 -1
- package/dist/src/functions/commands/dev.js +1 -1
- package/dist/src/functions/commands/enable.d.ts +8 -8
- package/dist/src/functions/commands/new-addon.js +2 -2
- package/dist/src/functions/commands/persona-sync.d.ts +1 -1
- package/dist/src/functions/commands/persona-sync.js +1 -1
- package/dist/src/functions/commands/persona.js +16 -18
- package/dist/src/functions/commands/scenario-ladder.js +1 -1
- package/dist/src/functions/commands/scenario.js +4 -2
- package/dist/src/functions/commands/scopes-shared.d.ts +4 -0
- package/dist/src/functions/commands/scopes-shared.js +1 -1
- package/dist/src/functions/commands/serve.js +1 -1
- package/dist/src/functions/commands/versions-check.js +3 -3
- package/dist/src/functions/db/annotation-parser.d.ts +14 -0
- package/dist/src/functions/db/annotation-parser.js +50 -2
- package/dist/src/functions/db/better-auth-schema.js +2 -1
- package/dist/src/functions/db/db-codegen.d.ts +12 -0
- package/dist/src/functions/db/db-codegen.js +149 -16
- package/dist/src/functions/db/local-db.d.ts +87 -6
- package/dist/src/functions/db/local-db.js +202 -35
- package/dist/src/functions/db/sqlite/dev-seed.d.ts +12 -0
- package/dist/src/functions/db/sqlite/dev-seed.js +23 -0
- package/dist/src/functions/db/sqlite/sqlite-runtime-bun.js +1 -1
- package/dist/src/functions/validate/bootstrap-checks.d.ts +19 -0
- package/dist/src/functions/validate/bootstrap-checks.js +90 -0
- package/dist/src/functions/validate/persona-checks.d.ts +24 -0
- package/dist/src/functions/validate/persona-checks.js +136 -0
- package/dist/src/functions/validate/scenario-file-checks.d.ts +17 -0
- package/dist/src/functions/validate/scenario-file-checks.js +123 -0
- package/dist/src/functions/validate/shared-checks.d.ts +59 -0
- package/dist/src/functions/validate/shared-checks.js +311 -0
- package/dist/src/functions/validate/workspace-validate.d.ts +1 -2
- package/dist/src/functions/validate/workspace-validate.js +4 -196
- package/dist/src/functions/wirings/auth/serialize-auth-gen.js +16 -1
- package/dist/src/functions/wirings/auth/serialize-auth-types.d.ts +3 -1
- package/dist/src/functions/wirings/auth/serialize-auth-types.js +3 -1
- package/dist/src/functions/wirings/channels/pikku-channels.d.ts +1 -1
- package/dist/src/functions/wirings/channels/serialize-typed-channel-map.js +10 -2
- package/dist/src/functions/wirings/cli/pikku-command-cli-entry.js +9 -4
- package/dist/src/functions/wirings/cli/serialize-channel-cli-client.js +32 -10
- package/dist/src/functions/wirings/cli/serialize-cli-entrypoint-guard.d.ts +12 -0
- package/dist/src/functions/wirings/cli/serialize-cli-entrypoint-guard.js +28 -0
- package/dist/src/functions/wirings/cli/serialize-local-cli-bootstrap.js +4 -1
- package/dist/src/functions/wirings/console/serialize-console-functions.js +1 -1
- package/dist/src/functions/wirings/functions/serialize-addon-types.js +1 -1
- package/dist/src/functions/wirings/functions/serialize-function-types.js +19 -5
- package/dist/src/functions/wirings/package/pikku-command-package-types.d.ts +3 -3
- package/dist/src/functions/wirings/personas/personas-secrets-file.d.ts +8 -0
- package/dist/src/functions/wirings/personas/personas-secrets-file.js +9 -0
- package/dist/src/functions/wirings/personas/pikku-command-personas.js +14 -0
- package/dist/src/functions/wirings/personas/serialize-personas-secrets.d.ts +16 -0
- package/dist/src/functions/wirings/personas/serialize-personas-secrets.js +34 -0
- package/dist/src/functions/wirings/queue/serialize-queue-map.js +2 -1
- package/dist/src/functions/wirings/queue/serialize-queue-wrapper.js +2 -1
- package/dist/src/functions/wirings/virtual-user/pikku-command-virtual-user-functions.d.ts +1 -0
- package/dist/src/functions/wirings/virtual-user/pikku-command-virtual-user-functions.js +42 -0
- package/dist/src/functions/wirings/virtual-user/serialize-virtual-user-functions.d.ts +28 -0
- package/dist/src/functions/wirings/virtual-user/serialize-virtual-user-functions.js +439 -0
- package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +2 -2
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +5 -11
- package/dist/src/functions/workflows/all.workflow.js +49 -16
- package/dist/src/server/bun-server-runner.d.ts +2 -2
- package/dist/src/server/bun-server-runner.js +5 -1
- package/dist/src/services/cli-logger.service.d.ts +28 -16
- package/dist/src/services/cli-logger.service.js +25 -16
- package/dist/src/services.js +8 -0
- package/dist/src/utils/file-import-path.js +9 -0
- package/dist/src/utils/pikku-cli-config.js +7 -0
- package/dist/src/utils/resolve-personas.d.ts +4 -9
- package/dist/src/utils/resolve-personas.js +4 -15
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/dist/.pikku/schemas/schemas/BootstrapInput.schema.json +0 -1
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +0 -1
- package/dist/src/functions/commands/db-seed.d.ts +0 -1
- package/dist/src/functions/commands/db-seed.js +0 -24
- package/dist/src/functions/db/sqlite/seed.d.ts +0 -11
- package/dist/src/functions/db/sqlite/seed.js +0 -22
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { rm } from 'node:fs/promises';
|
|
1
3
|
import { pikkuSessionlessFunc } from '#pikku';
|
|
2
4
|
import { writeFileInDir } from '../../../utils/file-writer.js';
|
|
3
5
|
import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
|
|
4
6
|
import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
|
|
5
7
|
import { serializePersonasTypes } from './serialize-personas-types.js';
|
|
8
|
+
import { serializePersonasSecrets } from './serialize-personas-secrets.js';
|
|
9
|
+
import { personasSecretsFilePath } from './personas-secrets-file.js';
|
|
6
10
|
import { personaEnvironmentErrors, validateAndBuildPersonasMeta, } from '@pikku/core/persona';
|
|
7
11
|
export const pikkuPersonas = pikkuSessionlessFunc({
|
|
8
12
|
func: async ({ logger, config, getInspectorState }, data) => {
|
|
@@ -29,6 +33,16 @@ export const pikkuPersonas = pikkuSessionlessFunc({
|
|
|
29
33
|
environmentNames: Object.keys(environments),
|
|
30
34
|
});
|
|
31
35
|
await writeFileInDir(logger, personasFile, content);
|
|
36
|
+
// The actor sign-in secret only means anything once someone is declared
|
|
37
|
+
// to sign in. Drop the file again when the last persona goes, so a stale
|
|
38
|
+
// declaration can't keep asking a deploy for a value nothing reads.
|
|
39
|
+
const secretsFile = personasSecretsFilePath(personasFile);
|
|
40
|
+
if (state.personas.definitions.length > 0) {
|
|
41
|
+
await writeFileInDir(logger, secretsFile, serializePersonasSecrets());
|
|
42
|
+
}
|
|
43
|
+
else if (existsSync(secretsFile)) {
|
|
44
|
+
await rm(secretsFile);
|
|
45
|
+
}
|
|
32
46
|
if (personasMetaJsonFile) {
|
|
33
47
|
const meta = validateAndBuildPersonasMeta(state.personas.definitions);
|
|
34
48
|
await writeFileInDir(logger, personasMetaJsonFile, JSON.stringify(meta, null, 2));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the sibling secrets file for a project that declares personas.
|
|
3
|
+
*
|
|
4
|
+
* `SCENARIO_ACTOR_SECRET` is the platform's, not the application's: nothing in
|
|
5
|
+
* app code reads it. It is consumed by the scenario service when it signs an
|
|
6
|
+
* actor in, by `pikku scenario` / `pikku persona` / `pikku persona sync`, and by
|
|
7
|
+
* the Playwright provider. Declaring it here — rather than leaving every project
|
|
8
|
+
* to hand-write the same `defineSecret` — is the same reasoning that puts
|
|
9
|
+
* `BETTER_AUTH_SECRET` in the auth scaffold: a value the framework requires
|
|
10
|
+
* regardless of what the user wired should be collected by the platform, not
|
|
11
|
+
* discovered missing at the first sign-in.
|
|
12
|
+
*
|
|
13
|
+
* The schema is a named const because the inspector only accepts a variable
|
|
14
|
+
* reference for `schema`, never an inline expression.
|
|
15
|
+
*/
|
|
16
|
+
export declare const serializePersonasSecrets: () => string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the sibling secrets file for a project that declares personas.
|
|
3
|
+
*
|
|
4
|
+
* `SCENARIO_ACTOR_SECRET` is the platform's, not the application's: nothing in
|
|
5
|
+
* app code reads it. It is consumed by the scenario service when it signs an
|
|
6
|
+
* actor in, by `pikku scenario` / `pikku persona` / `pikku persona sync`, and by
|
|
7
|
+
* the Playwright provider. Declaring it here — rather than leaving every project
|
|
8
|
+
* to hand-write the same `defineSecret` — is the same reasoning that puts
|
|
9
|
+
* `BETTER_AUTH_SECRET` in the auth scaffold: a value the framework requires
|
|
10
|
+
* regardless of what the user wired should be collected by the platform, not
|
|
11
|
+
* discovered missing at the first sign-in.
|
|
12
|
+
*
|
|
13
|
+
* The schema is a named const because the inspector only accepts a variable
|
|
14
|
+
* reference for `schema`, never an inline expression.
|
|
15
|
+
*/
|
|
16
|
+
export const serializePersonasSecrets = () => {
|
|
17
|
+
return `/**
|
|
18
|
+
* This file was generated by the @pikku/cli
|
|
19
|
+
*/
|
|
20
|
+
import { defineSecret } from '@pikku/core/secret'
|
|
21
|
+
import { z } from 'zod'
|
|
22
|
+
|
|
23
|
+
export const ScenarioActorSecretSchema = z.string()
|
|
24
|
+
|
|
25
|
+
defineSecret({
|
|
26
|
+
name: 'scenarioActorSecret',
|
|
27
|
+
displayName: 'Scenario Actor Secret',
|
|
28
|
+
description:
|
|
29
|
+
'Signing secret that lets scenarios and virtual users sign in as a declared persona',
|
|
30
|
+
secretId: 'SCENARIO_ACTOR_SECRET',
|
|
31
|
+
schema: ScenarioActorSecretSchema,
|
|
32
|
+
})
|
|
33
|
+
`;
|
|
34
|
+
};
|
|
@@ -13,6 +13,7 @@ ${serializedImportMap}
|
|
|
13
13
|
${serializedCustomTypes}
|
|
14
14
|
|
|
15
15
|
import type { QueueJob } from '@pikku/core/queue'
|
|
16
|
+
import type { Safe } from '@pikku/core'
|
|
16
17
|
|
|
17
18
|
interface QueueHandler<I, O> {
|
|
18
19
|
input: I;
|
|
@@ -23,7 +24,7 @@ ${serializedQueues}
|
|
|
23
24
|
|
|
24
25
|
type QueueAdd = <Name extends keyof QueueMap>(
|
|
25
26
|
name: Name,
|
|
26
|
-
data: QueueMap[Name]['input']
|
|
27
|
+
data: Safe<QueueMap[Name]['input']>,
|
|
27
28
|
options?: {
|
|
28
29
|
priority?: number
|
|
29
30
|
delay?: number
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const serializeQueueWrapper = (queueMapPath) => {
|
|
2
2
|
return `
|
|
3
3
|
import type { QueueService, QueueJob } from '@pikku/core/queue'
|
|
4
|
+
import type { Safe } from '@pikku/core'
|
|
4
5
|
import type { QueueMap, TypedPikkuQueue } from '${queueMapPath}'
|
|
5
6
|
|
|
6
7
|
export class PikkuQueue implements TypedPikkuQueue {
|
|
@@ -8,7 +9,7 @@ export class PikkuQueue implements TypedPikkuQueue {
|
|
|
8
9
|
|
|
9
10
|
public async add<Name extends keyof QueueMap>(
|
|
10
11
|
queueName: Name,
|
|
11
|
-
data: QueueMap[Name]['input']
|
|
12
|
+
data: Safe<QueueMap[Name]['input']>,
|
|
12
13
|
options?: {
|
|
13
14
|
priority?: number
|
|
14
15
|
delay?: number
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const pikkuVirtualUserFunctions: import("#pikku").PikkuFunctionConfig<void, boolean, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<void, boolean, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<void, boolean, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { pikkuSessionlessFunc } from '#pikku';
|
|
2
|
+
import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
|
|
3
|
+
import { writeFileInDir } from '../../../utils/file-writer.js';
|
|
4
|
+
import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
|
|
5
|
+
import { serializeVirtualUserFunctions } from './serialize-virtual-user-functions.js';
|
|
6
|
+
import { resolveScaffoldFeature } from '../../../utils/resolve-scaffold-feature.js';
|
|
7
|
+
export const pikkuVirtualUserFunctions = pikkuSessionlessFunc({
|
|
8
|
+
func: async ({ logger, config, getInspectorState }) => {
|
|
9
|
+
if (!config.scaffold?.virtualUser ||
|
|
10
|
+
!config.virtualUserFunctionsFile ||
|
|
11
|
+
!config.virtualUserSchemasFile) {
|
|
12
|
+
logger.debug({
|
|
13
|
+
message: 'Skipping virtual user scaffold (set scaffold.virtualUser in pikku.config.json to enable).',
|
|
14
|
+
type: 'skip',
|
|
15
|
+
});
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const state = await getInspectorState();
|
|
19
|
+
// A virtual user IS a persona — the generated functions sign in as one and
|
|
20
|
+
// run as them. With none declared the scaffold would emit RPCs whose only
|
|
21
|
+
// possible answer is "unknown persona". Fail the codegen instead of
|
|
22
|
+
// shipping that.
|
|
23
|
+
if (Object.keys(state.personas?.definitions ?? {}).length === 0) {
|
|
24
|
+
throw new Error(`"scaffold.virtualUser" is enabled but no personas are declared.\n` +
|
|
25
|
+
`A virtual user runs AS a persona, so there is nobody to run.\n` +
|
|
26
|
+
`Fix: declare one with definePersonas({ ... }), or remove ` +
|
|
27
|
+
`"scaffold.virtualUser" from pikku.config.json.`);
|
|
28
|
+
}
|
|
29
|
+
const pathToPikkuTypes = getFileImportRelativePath(config.virtualUserFunctionsFile, config.typesDeclarationFile, config.packageMappings);
|
|
30
|
+
const pathToPersonas = getFileImportRelativePath(config.virtualUserFunctionsFile, config.personasWiringFile, config.packageMappings);
|
|
31
|
+
const { schemas, functions } = serializeVirtualUserFunctions(pathToPikkuTypes, pathToPersonas, resolveScaffoldFeature('virtualUser', config.scaffold.virtualUser).auth);
|
|
32
|
+
await writeFileInDir(logger, config.virtualUserSchemasFile, schemas);
|
|
33
|
+
await writeFileInDir(logger, config.virtualUserFunctionsFile, functions);
|
|
34
|
+
return true;
|
|
35
|
+
},
|
|
36
|
+
middleware: [
|
|
37
|
+
logCommandInfoAndTime({
|
|
38
|
+
commandStart: 'Generating virtual user functions',
|
|
39
|
+
commandEnd: 'Generated virtual user functions',
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface VirtualUserGenOutput {
|
|
2
|
+
schemas: string;
|
|
3
|
+
functions: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Generate the virtual-user functions into the project scaffold: start a run,
|
|
7
|
+
* read one back, and the sessionless function that does the work.
|
|
8
|
+
*
|
|
9
|
+
* Scaffolded rather than shipped in an addon because the whole feature is
|
|
10
|
+
* derived from what the project already generates — its personas, its function
|
|
11
|
+
* meta, its scenarios — so there is nothing to configure and nothing to author.
|
|
12
|
+
* `pikku persona run` does the same thing from a terminal; this is the same run
|
|
13
|
+
* started over RPC, from CI or from a console, with the result kept.
|
|
14
|
+
*
|
|
15
|
+
* A virtual user is NOT a workflow and NOT a queued job. It explores, so no two
|
|
16
|
+
* attempts take the same steps and there is nothing to replay; and the run
|
|
17
|
+
* record already carries the progress a queue would only be holding on the way
|
|
18
|
+
* here. So `runVirtualUser` writes the record, kicks the run off without
|
|
19
|
+
* awaiting it, and returns the id. The one cost is stated on the record: a
|
|
20
|
+
* restart mid-run strands it at `running`.
|
|
21
|
+
*
|
|
22
|
+
* Emitted as two files. The schemas are zod, and the inspector reads a zod
|
|
23
|
+
* schema by importing the module that declares it — which it cannot do for the
|
|
24
|
+
* functions file, whose relative pikku-types import per-unit deploy codegen
|
|
25
|
+
* rewrites. Keeping the schemas in a sibling module that imports nothing but
|
|
26
|
+
* zod sidesteps that entirely.
|
|
27
|
+
*/
|
|
28
|
+
export declare const serializeVirtualUserFunctions: (pathToPikkuTypes: string, pathToPersonas: string, requireAuth?: boolean) => VirtualUserGenOutput;
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the virtual-user functions into the project scaffold: start a run,
|
|
3
|
+
* read one back, and the sessionless function that does the work.
|
|
4
|
+
*
|
|
5
|
+
* Scaffolded rather than shipped in an addon because the whole feature is
|
|
6
|
+
* derived from what the project already generates — its personas, its function
|
|
7
|
+
* meta, its scenarios — so there is nothing to configure and nothing to author.
|
|
8
|
+
* `pikku persona run` does the same thing from a terminal; this is the same run
|
|
9
|
+
* started over RPC, from CI or from a console, with the result kept.
|
|
10
|
+
*
|
|
11
|
+
* A virtual user is NOT a workflow and NOT a queued job. It explores, so no two
|
|
12
|
+
* attempts take the same steps and there is nothing to replay; and the run
|
|
13
|
+
* record already carries the progress a queue would only be holding on the way
|
|
14
|
+
* here. So `runVirtualUser` writes the record, kicks the run off without
|
|
15
|
+
* awaiting it, and returns the id. The one cost is stated on the record: a
|
|
16
|
+
* restart mid-run strands it at `running`.
|
|
17
|
+
*
|
|
18
|
+
* Emitted as two files. The schemas are zod, and the inspector reads a zod
|
|
19
|
+
* schema by importing the module that declares it — which it cannot do for the
|
|
20
|
+
* functions file, whose relative pikku-types import per-unit deploy codegen
|
|
21
|
+
* rewrites. Keeping the schemas in a sibling module that imports nothing but
|
|
22
|
+
* zod sidesteps that entirely.
|
|
23
|
+
*/
|
|
24
|
+
export const serializeVirtualUserFunctions = (pathToPikkuTypes, pathToPersonas, requireAuth = true) => {
|
|
25
|
+
const authFlag = requireAuth ? 'true' : 'false';
|
|
26
|
+
const schemas = `/**
|
|
27
|
+
* Auto-generated virtual user schemas
|
|
28
|
+
* Do not edit manually - regenerate with 'npx pikku'
|
|
29
|
+
*/
|
|
30
|
+
import { z } from 'zod'
|
|
31
|
+
|
|
32
|
+
/** Every disposition \`DISPOSITIONS\` declares. */
|
|
33
|
+
export const Disposition = z.enum([
|
|
34
|
+
'realistic',
|
|
35
|
+
'careless',
|
|
36
|
+
'newcomer',
|
|
37
|
+
'stale',
|
|
38
|
+
'auditor',
|
|
39
|
+
'adversarial',
|
|
40
|
+
'accountable',
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
export const RunVirtualUserInput = z.object({
|
|
44
|
+
/** A persona id from \`definePersonas()\`. */
|
|
45
|
+
persona: z.string().min(1),
|
|
46
|
+
/**
|
|
47
|
+
* Situational goals in the caller's own words, run alongside the intents
|
|
48
|
+
* derived from the app's scenarios. Appended to what the persona durably
|
|
49
|
+
* wants, never a replacement — a run that replaces their goals is not them.
|
|
50
|
+
*/
|
|
51
|
+
goals: z.array(z.string().min(1)).max(20).optional(),
|
|
52
|
+
/**
|
|
53
|
+
* Ids and slugs from an earlier run, replayed as this user's notes on turn
|
|
54
|
+
* one. The point of a \`stale\` run: some of them no longer resolve, and what
|
|
55
|
+
* the product does about that is the thing being tested.
|
|
56
|
+
*/
|
|
57
|
+
memory: z.record(z.string(), z.string()).optional(),
|
|
58
|
+
/** Overrides the persona's declared disposition for this run only. */
|
|
59
|
+
disposition: Disposition.optional(),
|
|
60
|
+
budget: z
|
|
61
|
+
.object({
|
|
62
|
+
steps: z.number().int().min(1).max(500).optional(),
|
|
63
|
+
mutations: z.number().int().min(0).max(500).optional(),
|
|
64
|
+
durationMs: z.number().int().min(1000).max(3_600_000).optional(),
|
|
65
|
+
})
|
|
66
|
+
.optional(),
|
|
67
|
+
/** Fixed seed, so a run replays into the same finding. */
|
|
68
|
+
seed: z.number().int().optional(),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
export const RunVirtualUserOutput = z.object({
|
|
72
|
+
runId: z.string(),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export const GetVirtualUserRunInput = z.object({
|
|
76
|
+
runId: z.string(),
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* What the user noticed. \`kind\` is a plain string rather than an enum of
|
|
81
|
+
* today's finding kinds: a run that turns up something new should be readable
|
|
82
|
+
* by a client built before that kind existed.
|
|
83
|
+
*/
|
|
84
|
+
export const Finding = z.object({
|
|
85
|
+
kind: z.string(),
|
|
86
|
+
detail: z.string(),
|
|
87
|
+
rpcName: z.string().optional(),
|
|
88
|
+
status: z.number().optional(),
|
|
89
|
+
intentId: z.string().optional(),
|
|
90
|
+
/** Step index within the run, so the transcript can be replayed to here. */
|
|
91
|
+
step: z.number(),
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
export const GetVirtualUserRunOutput = z.object({
|
|
95
|
+
runId: z.string(),
|
|
96
|
+
persona: z.string(),
|
|
97
|
+
disposition: Disposition,
|
|
98
|
+
seed: z.number(),
|
|
99
|
+
status: z.enum(['running', 'completed', 'failed']),
|
|
100
|
+
goals: z.array(z.string()),
|
|
101
|
+
memory: z.record(z.string(), z.string()),
|
|
102
|
+
findings: z.array(Finding),
|
|
103
|
+
/** Steps, calls, mutations, tokens and elapsed time, as the engine counted them. */
|
|
104
|
+
tally: z.record(z.string(), z.unknown()).nullable(),
|
|
105
|
+
stoppedBy: z.string().nullable(),
|
|
106
|
+
error: z.string().nullable(),
|
|
107
|
+
createdAt: z.string(),
|
|
108
|
+
finishedAt: z.string().nullable(),
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
/** The internal dispatch payload — everything the run was resolved down to. */
|
|
112
|
+
export const ExecuteVirtualUserRunInput = z.object({
|
|
113
|
+
runId: z.string(),
|
|
114
|
+
persona: z.string(),
|
|
115
|
+
disposition: Disposition,
|
|
116
|
+
goals: z.array(z.string()),
|
|
117
|
+
memory: z.record(z.string(), z.string()),
|
|
118
|
+
budget: z
|
|
119
|
+
.object({
|
|
120
|
+
steps: z.number().optional(),
|
|
121
|
+
mutations: z.number().optional(),
|
|
122
|
+
durationMs: z.number().optional(),
|
|
123
|
+
})
|
|
124
|
+
.optional(),
|
|
125
|
+
seed: z.number(),
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
export const ExecuteVirtualUserRunOutput = z.object({
|
|
129
|
+
findings: z.number(),
|
|
130
|
+
})
|
|
131
|
+
`;
|
|
132
|
+
const functions = `/**
|
|
133
|
+
* Auto-generated virtual user functions
|
|
134
|
+
* Do not edit manually - regenerate with 'npx pikku'
|
|
135
|
+
*/
|
|
136
|
+
import { pikkuFunc, pikkuSessionlessFunc, defineScope } from '${pathToPikkuTypes}'
|
|
137
|
+
import {
|
|
138
|
+
personaVirtualUserTarget,
|
|
139
|
+
prepareVirtualUserRun,
|
|
140
|
+
runVirtualUser as runVirtualUserEngine,
|
|
141
|
+
PRODUCTION_DISPOSITION,
|
|
142
|
+
type SchemaMap,
|
|
143
|
+
type VirtualUserDisposition,
|
|
144
|
+
} from '@pikku/core/virtual-user'
|
|
145
|
+
import { createPersonas, personaConfigs } from '${pathToPersonas}'
|
|
146
|
+
import {
|
|
147
|
+
ExecuteVirtualUserRunInput,
|
|
148
|
+
ExecuteVirtualUserRunOutput,
|
|
149
|
+
GetVirtualUserRunInput,
|
|
150
|
+
GetVirtualUserRunOutput,
|
|
151
|
+
RunVirtualUserInput,
|
|
152
|
+
RunVirtualUserOutput,
|
|
153
|
+
} from './virtual-user.schemas.gen.js'
|
|
154
|
+
|
|
155
|
+
defineScope({
|
|
156
|
+
virtualUser: {
|
|
157
|
+
displayName: 'Virtual Users',
|
|
158
|
+
description: 'Run personas against this application and read what they found',
|
|
159
|
+
scopes: {
|
|
160
|
+
run: { description: 'Start a virtual user run' },
|
|
161
|
+
read: { description: "Read a run's findings" },
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Where the virtual user signs in. Its own variable rather than a guess at the
|
|
168
|
+
* host's origin: a run drives real traffic through the real front door, and a
|
|
169
|
+
* server that cannot name its own public URL would be signing in somewhere it
|
|
170
|
+
* only assumed was itself.
|
|
171
|
+
*/
|
|
172
|
+
const API_URL_VARIABLE = 'VIRTUAL_USER_API_URL'
|
|
173
|
+
const SECRET_VARIABLE = 'SCENARIO_ACTOR_SECRET'
|
|
174
|
+
const MODEL_VARIABLE = 'VIRTUAL_USER_MODEL'
|
|
175
|
+
|
|
176
|
+
export const runVirtualUser = pikkuFunc({
|
|
177
|
+
tags: ['pikku'],
|
|
178
|
+
title: 'Run a Virtual User',
|
|
179
|
+
description:
|
|
180
|
+
'Turns a declared persona loose on this application and records what it finds. Returns immediately with a run id; read the result back with getVirtualUserRun.',
|
|
181
|
+
expose: true,
|
|
182
|
+
auth: ${authFlag},
|
|
183
|
+
scopes: ['virtualUser:run'],
|
|
184
|
+
input: RunVirtualUserInput,
|
|
185
|
+
output: RunVirtualUserOutput,
|
|
186
|
+
func: async (
|
|
187
|
+
{ virtualUserRunStore, config },
|
|
188
|
+
input,
|
|
189
|
+
{ session, rpc }
|
|
190
|
+
) => {
|
|
191
|
+
if (!virtualUserRunStore) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
'No virtualUserRunStore is wired — a run has nowhere to be recorded. ' +
|
|
194
|
+
'Wire KyselyVirtualUserRunStore from @pikku/kysely, or your own implementation of VirtualUserRunStore.'
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const persona = personaConfigs[input.persona as keyof typeof personaConfigs] as
|
|
199
|
+
| { id: string; runnable: boolean; disposition?: string }
|
|
200
|
+
| undefined
|
|
201
|
+
if (!persona) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
\`Unknown persona "\${input.persona}" — declare it with definePersonas()\`
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
// An acted-upon persona has no session of its own, and running one would
|
|
207
|
+
// race whatever scenario acts on it.
|
|
208
|
+
if (!persona.runnable) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
\`Persona "\${input.persona}" is declared as acted upon, never run\`
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const disposition = (input.disposition ??
|
|
215
|
+
persona.disposition ??
|
|
216
|
+
'realistic') as VirtualUserDisposition
|
|
217
|
+
// Every disposition other than this one exists to find out what the product
|
|
218
|
+
// does wrong, which is not a thing to do to real customers' data. Checked
|
|
219
|
+
// against the effective disposition, so the override cannot smuggle one in.
|
|
220
|
+
if (
|
|
221
|
+
config.nodeEnv === 'production' &&
|
|
222
|
+
disposition !== PRODUCTION_DISPOSITION
|
|
223
|
+
) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
\`Only the '\${PRODUCTION_DISPOSITION}' disposition may run against production; "\${input.persona}" is \${disposition}\`
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Seeded here rather than inside the engine so the record carries the seed
|
|
230
|
+
// even if the run dies before returning — an unreproducible crash costs the
|
|
231
|
+
// most.
|
|
232
|
+
const seed = input.seed ?? Math.floor(Math.random() * 2_147_483_647)
|
|
233
|
+
|
|
234
|
+
const runId = await virtualUserRunStore.start({
|
|
235
|
+
persona: persona.id,
|
|
236
|
+
disposition,
|
|
237
|
+
seed,
|
|
238
|
+
goals: input.goals ?? [],
|
|
239
|
+
memory: input.memory ?? {},
|
|
240
|
+
startedBy: session?.userId ?? null,
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// Deliberately not awaited: a run takes minutes, and a held-open request
|
|
244
|
+
// survives neither a rollout nor a proxy timeout. executeVirtualUserRun
|
|
245
|
+
// writes both outcomes to the record itself, so the only thing left to
|
|
246
|
+
// handle here is a rejection escaping the promise — without the catch it
|
|
247
|
+
// becomes an unhandled rejection and takes the process with it.
|
|
248
|
+
void rpc!
|
|
249
|
+
.invoke('executeVirtualUserRun', {
|
|
250
|
+
runId,
|
|
251
|
+
persona: persona.id,
|
|
252
|
+
disposition,
|
|
253
|
+
goals: input.goals ?? [],
|
|
254
|
+
memory: input.memory ?? {},
|
|
255
|
+
budget: input.budget,
|
|
256
|
+
seed,
|
|
257
|
+
})
|
|
258
|
+
.catch(() => {})
|
|
259
|
+
|
|
260
|
+
return { runId }
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
export const getVirtualUserRun = pikkuFunc({
|
|
265
|
+
tags: ['pikku'],
|
|
266
|
+
title: 'Read a Virtual User Run',
|
|
267
|
+
description:
|
|
268
|
+
'Reads a run back: its status, what it found, and the counts the engine kept.',
|
|
269
|
+
expose: true,
|
|
270
|
+
auth: ${authFlag},
|
|
271
|
+
// Its own scope, and not the one that starts a run: an adversarial run's
|
|
272
|
+
// findings are working exploits carrying live ids, so reading them is the
|
|
273
|
+
// more sensitive of the two.
|
|
274
|
+
scopes: ['virtualUser:read'],
|
|
275
|
+
input: GetVirtualUserRunInput,
|
|
276
|
+
output: GetVirtualUserRunOutput,
|
|
277
|
+
func: async ({ virtualUserRunStore }, { runId }) => {
|
|
278
|
+
if (!virtualUserRunStore) {
|
|
279
|
+
throw new Error('No virtualUserRunStore is wired — there are no runs to read.')
|
|
280
|
+
}
|
|
281
|
+
const run = await virtualUserRunStore.get(runId)
|
|
282
|
+
if (!run) {
|
|
283
|
+
throw new Error(\`No virtual user run \${runId}\`)
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
runId: run.runId,
|
|
287
|
+
persona: run.persona,
|
|
288
|
+
disposition: run.disposition,
|
|
289
|
+
seed: run.seed,
|
|
290
|
+
status: run.status,
|
|
291
|
+
goals: run.goals,
|
|
292
|
+
memory: run.memory,
|
|
293
|
+
// Findings are free-form by design — the engine records what it noticed,
|
|
294
|
+
// not a fixed row shape — so they cross the wire as the schema's open
|
|
295
|
+
// object rather than being narrowed to whatever kinds exist today.
|
|
296
|
+
findings: run.findings.map((finding) => ({
|
|
297
|
+
kind: finding.kind as string,
|
|
298
|
+
detail: finding.detail,
|
|
299
|
+
rpcName: finding.rpcName,
|
|
300
|
+
status: finding.status,
|
|
301
|
+
intentId: finding.intentId,
|
|
302
|
+
step: finding.step,
|
|
303
|
+
})),
|
|
304
|
+
tally: (run.tally ?? null) as Record<string, unknown> | null,
|
|
305
|
+
stoppedBy: run.stoppedBy,
|
|
306
|
+
error: run.error,
|
|
307
|
+
createdAt: run.createdAt.toISOString(),
|
|
308
|
+
finishedAt: run.finishedAt ? run.finishedAt.toISOString() : null,
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* The run itself. Not exposed: it is dispatched by \`runVirtualUser\` and has no
|
|
315
|
+
* caller of its own.
|
|
316
|
+
*
|
|
317
|
+
* Everything it needs is derived through \`metaService\` and the generated
|
|
318
|
+
* personas — the same public surface any consumer has. Nothing reaches into
|
|
319
|
+
* pikku's internals, because an app could not, and a feature built on what only
|
|
320
|
+
* the framework can see would not be this feature.
|
|
321
|
+
*/
|
|
322
|
+
export const executeVirtualUserRun = pikkuSessionlessFunc({
|
|
323
|
+
tags: ['pikku'],
|
|
324
|
+
input: ExecuteVirtualUserRunInput,
|
|
325
|
+
output: ExecuteVirtualUserRunOutput,
|
|
326
|
+
func: async (
|
|
327
|
+
{ virtualUserRunStore, metaService, aiAgentRunner, variables, logger },
|
|
328
|
+
{ runId, persona: personaId, disposition, goals, memory, budget, seed }
|
|
329
|
+
) => {
|
|
330
|
+
if (!virtualUserRunStore) {
|
|
331
|
+
throw new Error('No virtualUserRunStore is wired.')
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
if (!metaService) {
|
|
335
|
+
throw new Error('metaService is not wired — there is no catalogue to derive')
|
|
336
|
+
}
|
|
337
|
+
if (!aiAgentRunner) {
|
|
338
|
+
throw new Error('aiAgentRunner is not wired — there is nothing to think with')
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const apiUrl = await variables.get(API_URL_VARIABLE)
|
|
342
|
+
if (!apiUrl) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
\`\${API_URL_VARIABLE} is not set — a virtual user has no address to sign in at.\`
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
const secret = await variables.get(SECRET_VARIABLE)
|
|
348
|
+
if (!secret) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
\`\${SECRET_VARIABLE} is not set — actor sign-in is disabled, so there is nobody for the virtual user to be.\`
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
const model = await variables.get(MODEL_VARIABLE)
|
|
354
|
+
if (!model) {
|
|
355
|
+
throw new Error(\`\${MODEL_VARIABLE} is not set — no model to think with.\`)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const functionsMeta = await metaService.getFunctionsMeta()
|
|
359
|
+
// Only the schemas the catalogue can actually refer to, so a large app
|
|
360
|
+
// does not pull every schema it has ever generated into one run.
|
|
361
|
+
const schemaNames = [
|
|
362
|
+
...new Set(
|
|
363
|
+
Object.values(functionsMeta).flatMap((meta: any) =>
|
|
364
|
+
[meta.inputSchemaName, meta.outputSchemaName].filter(
|
|
365
|
+
(name: unknown): name is string => !!name
|
|
366
|
+
)
|
|
367
|
+
)
|
|
368
|
+
),
|
|
369
|
+
]
|
|
370
|
+
const schemas = (await metaService.getSchemas(schemaNames)) as SchemaMap
|
|
371
|
+
|
|
372
|
+
const persona = personaConfigs[personaId as keyof typeof personaConfigs]
|
|
373
|
+
if (!persona) {
|
|
374
|
+
throw new Error(\`Persona "\${personaId}" is no longer declared\`)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const { catalogue, intents, agents } = prepareVirtualUserRun({
|
|
378
|
+
persona,
|
|
379
|
+
functionsMeta,
|
|
380
|
+
schemas,
|
|
381
|
+
workflowsMeta: await metaService.getWorkflowMeta(),
|
|
382
|
+
systemRoles: await metaService.getSystemRolesMeta(),
|
|
383
|
+
agentsMeta: await metaService.getAgentsMeta(),
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
const signedIn = createPersonas({ apiUrl, secret, model })
|
|
387
|
+
const target = signedIn[personaId as keyof typeof signedIn]
|
|
388
|
+
if (!target) {
|
|
389
|
+
throw new Error(\`Persona "\${personaId}" cannot sign in\`)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const result = await runVirtualUserEngine({
|
|
393
|
+
persona,
|
|
394
|
+
personaId,
|
|
395
|
+
disposition: disposition as VirtualUserDisposition,
|
|
396
|
+
catalogue,
|
|
397
|
+
intents,
|
|
398
|
+
goals,
|
|
399
|
+
memory,
|
|
400
|
+
seed,
|
|
401
|
+
agents,
|
|
402
|
+
target: personaVirtualUserTarget(target, {
|
|
403
|
+
model,
|
|
404
|
+
agents: agents.map((agent) => agent.name),
|
|
405
|
+
}),
|
|
406
|
+
// \`AIAgentRunnerService.run\` IS the engine's \`ActorLLM\` — same params,
|
|
407
|
+
// same result — so a virtual user thinks through the same runner every
|
|
408
|
+
// agent in the app does, provider quirks and all.
|
|
409
|
+
llm: (params) => aiAgentRunner.run(params),
|
|
410
|
+
model,
|
|
411
|
+
budget: {
|
|
412
|
+
steps: budget?.steps,
|
|
413
|
+
mutations: budget?.mutations,
|
|
414
|
+
duration: budget?.durationMs,
|
|
415
|
+
},
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
await virtualUserRunStore.complete(runId, {
|
|
419
|
+
findings: result.findings,
|
|
420
|
+
tally: result.tally,
|
|
421
|
+
memory: result.memory,
|
|
422
|
+
stoppedBy: result.stoppedBy ?? null,
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
return { findings: result.findings.length }
|
|
426
|
+
} catch (error) {
|
|
427
|
+
// A crashed run and a run that found nothing are different states, and the
|
|
428
|
+
// record is the only place that distinction survives — leaving it at
|
|
429
|
+
// 'running' forever is what \`fail\` exists to prevent.
|
|
430
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
431
|
+
logger.error(\`Virtual user run \${runId} (\${personaId}) failed: \${message}\`)
|
|
432
|
+
await virtualUserRunStore.fail(runId, message)
|
|
433
|
+
throw error
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
})
|
|
437
|
+
`;
|
|
438
|
+
return { schemas, functions };
|
|
439
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generate the scenario step map: the typed set of names a scenario may pass to
|
|
3
|
-
* `scenario.
|
|
3
|
+
* `scenario.given/when/then`.
|
|
4
4
|
*
|
|
5
5
|
* Steps are referenced by string, exactly like `workflow.do` references an RPC,
|
|
6
6
|
* so type safety has to come from a generated map rather than from importing
|
|
@@ -54,7 +54,7 @@ export const serializeScenarioStepMap = (logger, relativeToPath, packageMappings
|
|
|
54
54
|
.filter((line) => !line.startsWith('import '))
|
|
55
55
|
.join('\n');
|
|
56
56
|
return `/**
|
|
57
|
-
* Scenario step map: the names \`scenario.
|
|
57
|
+
* Scenario step map: the names \`scenario.given/when/then\` accepts, with
|
|
58
58
|
* the input and output types of each step.
|
|
59
59
|
*/
|
|
60
60
|
|
|
@@ -43,18 +43,12 @@ export interface TypedWorkflow extends PikkuWorkflowWire {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* The typed half of a scenario wire: \`
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* The typed half of a scenario wire: \`given\`/\`when\`/\`then\`, narrowed to the
|
|
47
|
+
* names declared by \`pikkuScenarioStep\` in this project. \`given\` and \`when\`
|
|
48
|
+
* differ only in the prose the reporter renders; \`then\` additionally makes the
|
|
49
|
+
* step's bindings witnesses rather than alternatives.
|
|
49
50
|
*/
|
|
50
51
|
export interface TypedScenarioSteps {
|
|
51
|
-
step<K extends keyof FlattenedScenarioStepMap>(
|
|
52
|
-
stepName: string,
|
|
53
|
-
stepFunc: K,
|
|
54
|
-
data?: FlattenedScenarioStepMap[K]['input'],
|
|
55
|
-
options?: ScenarioStepOptions
|
|
56
|
-
): Promise<FlattenedScenarioStepMap[K]['output']>
|
|
57
|
-
|
|
58
52
|
given<K extends keyof FlattenedScenarioStepMap>(
|
|
59
53
|
stepName: string,
|
|
60
54
|
stepFunc: K,
|
|
@@ -329,7 +323,7 @@ export type PikkuScenarioStepConfigWithSchema<
|
|
|
329
323
|
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
|
330
324
|
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
|
331
325
|
> = {
|
|
332
|
-
/** Registered name — this is the string \`scenario.
|
|
326
|
+
/** Registered name — this is the string \`scenario.given()\` references. */
|
|
333
327
|
name: string
|
|
334
328
|
/**
|
|
335
329
|
* What this step does, for the console and for whoever reads the source. It
|