@pikku/inspector 0.12.102 → 0.12.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/add/add-cli.js +21 -11
  3. package/dist/add/add-define-screens.d.ts +8 -0
  4. package/dist/add/add-define-screens.js +140 -0
  5. package/dist/add/add-functions.js +8 -8
  6. package/dist/add/add-virtual-users.d.ts +13 -0
  7. package/dist/add/{add-personas.js → add-virtual-users.js} +33 -33
  8. package/dist/add/add-wire-addon.js +39 -2
  9. package/dist/add/add-workflow.js +14 -10
  10. package/dist/error-codes.d.ts +9 -3
  11. package/dist/error-codes.js +9 -3
  12. package/dist/index.d.ts +1 -1
  13. package/dist/inspector.js +7 -3
  14. package/dist/types.d.ts +45 -3
  15. package/dist/utils/extract-function-name.js +1 -1
  16. package/dist/utils/get-property-value.js +1 -1
  17. package/dist/utils/load-addon-functions-meta.d.ts +0 -5
  18. package/dist/utils/load-addon-functions-meta.js +53 -0
  19. package/dist/utils/post-process.d.ts +36 -34
  20. package/dist/utils/post-process.js +227 -169
  21. package/dist/utils/serialize-inspector-state.d.ts +7 -2
  22. package/dist/utils/serialize-inspector-state.js +10 -6
  23. package/dist/utils/single-declaration.d.ts +3 -3
  24. package/dist/utils/single-declaration.js +3 -3
  25. package/dist/utils/validate-single-step-workflows.d.ts +3 -0
  26. package/dist/utils/validate-single-step-workflows.js +32 -0
  27. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +3 -3
  28. package/dist/utils/workflow/dsl/patterns.d.ts +3 -3
  29. package/dist/utils/workflow/dsl/patterns.js +4 -4
  30. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +6 -6
  31. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +5 -5
  32. package/dist/visit.js +4 -2
  33. package/knowledge/decisions/a-channel-with-auth-false-marks-its-connect-and-disconnect-sessionless.md +22 -0
  34. package/knowledge/decisions/a-declared-scope-id-never-shadows-a-definition.md +20 -0
  35. package/knowledge/decisions/a-dsl-step-virtual-user-is-the-literal-name-in-its-options.md +27 -0
  36. package/knowledge/decisions/a-feature-flags-anyof-is-read-from-the-ast-and-an-empty-list-is-refused.md +20 -0
  37. package/knowledge/decisions/a-non-literal-string-array-reads-as-absent.md +25 -0
  38. package/knowledge/decisions/a-permission-with-an-underscore-data-parameter-takes-no-data.md +20 -0
  39. package/knowledge/decisions/a-provider-login-virtual-user-is-never-runnable.md +27 -0
  40. package/knowledge/decisions/a-remote-addon-bound-credential-or-secret-must-be-wired.md +27 -0
  41. package/knowledge/decisions/a-route-auth-overrides-its-groups-auth.md +22 -0
  42. package/knowledge/decisions/a-route-requires-a-session-when-any-auth-signal-says-so.md +29 -0
  43. package/knowledge/decisions/a-webhook-source-with-verify-gets-a-singleton-secret-credential.md +21 -0
  44. package/knowledge/decisions/a-wire-with-auth-disabled-cannot-point-at-a-pikkufunc.md +21 -0
  45. package/knowledge/decisions/addon-contracts-cannot-wire-or-declare-handlers.md +25 -0
  46. package/knowledge/decisions/addon-declarations-name-the-secrets-and-credentials-the-app-lends-it.md +19 -0
  47. package/knowledge/decisions/an-inline-wiring-function-is-sessionless-unless-built-with-pikkufunc.md +23 -0
  48. package/knowledge/decisions/better-auth-admin-plugin-is-refused.md +24 -0
  49. package/knowledge/decisions/define-screens-scopes-must-be-string-literals.md +18 -0
  50. package/knowledge/decisions/index.md +38 -0
  51. package/knowledge/decisions/inspector-records-a-non-literal-global-grant-as-source-text.md +20 -0
  52. package/knowledge/decisions/only-one-pikku-better-auth-export-per-codebase.md +19 -0
  53. package/knowledge/decisions/scope-references-and-secret-aliases-are-critical-diagnostics.md +23 -0
  54. package/knowledge/decisions/scope-segments-may-not-be-wildcards-or-contain-colons.md +23 -0
  55. package/knowledge/decisions/shorthand-permission-entries-are-extracted.md +22 -0
  56. package/knowledge/decisions/system-roles-declare-literal-unqualified-scopes.md +28 -0
  57. package/knowledge/decisions/the-inspector-records-agent-permissions-only-to-aggregate-services.md +14 -0
  58. package/knowledge/decisions/vault-secrets-reach-no-sink-and-pii-stays-out-of-logs-and-webhooks.md +30 -0
  59. package/knowledge/decisions/webhook-gateway-wrappers-are-sessionless-and-gateway-auth-runs-inside-the-handler.md +26 -0
  60. package/knowledge/index.md +14 -0
  61. package/knowledge/questions/does-a-non-literal-remote-addon-auth-get-checked.md +26 -0
  62. package/knowledge/questions/index.md +16 -0
  63. package/knowledge/questions/is-an-incomplete-oauth2-block-dropped-without-a-log.md +23 -0
  64. package/knowledge/questions/should-app-functions-named-like-scenario-instrumentation-be-refused.md +28 -0
  65. package/knowledge/questions/should-the-openapi-document-default-every-operation-to-a-bearer-or-api-key.md +22 -0
  66. package/package.json +3 -3
  67. package/dist/add/add-personas.d.ts +0 -13
@@ -1,11 +1,12 @@
1
1
  import * as ts from 'typescript';
2
+ import { relative } from 'node:path';
2
3
  import { extractFunctionName } from '../utils/extract-function-name.js';
3
4
  import { extractFunctionNode } from '../utils/extract-function-node.js';
4
5
  import { ErrorCode } from '../error-codes.js';
5
6
  import { extractStringLiteral, isStringLike, isFunctionLike, extractDescription, extractDuration, } from '../utils/extract-node-value.js';
6
7
  import { getCommonWireMetaData, getPropertyValue, } from '../utils/get-property-value.js';
7
8
  import { extractDSLWorkflow } from '../utils/workflow/dsl/extract-dsl-workflow.js';
8
- import { getSourceText, extractActorFromOptions, } from '../utils/workflow/dsl/patterns.js';
9
+ import { getSourceText, extractVirtualUserFromOptions, } from '../utils/workflow/dsl/patterns.js';
9
10
  /**
10
11
  * Extract a workflow step's display name without letting a non-static name
11
12
  * (e.g. a function call) abort the scan. The step name is cosmetic, so a
@@ -141,7 +142,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
141
142
  type: 'rpc',
142
143
  stepName,
143
144
  rpcName,
144
- actor: extractActorFromOptions(optionsArg),
145
+ virtualUser: extractVirtualUserFromOptions(optionsArg),
145
146
  });
146
147
  state.rpc.invokedFunctions.add(rpcName);
147
148
  }
@@ -164,7 +165,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
164
165
  stepName,
165
166
  rpcName,
166
167
  expectEventually: true,
167
- actor: extractActorFromOptions(args.length >= 5 ? args[4] : undefined),
168
+ virtualUser: extractVirtualUserFromOptions(args.length >= 5 ? args[4] : undefined),
168
169
  });
169
170
  state.rpc.invokedFunctions.add(rpcName);
170
171
  }
@@ -229,7 +230,7 @@ export const addWorkflow = (logger, node, checker, state, options) => {
229
230
  wrapperType = 'complex';
230
231
  }
231
232
  else if (expression.text === 'pikkuScenario') {
232
- // A scenario is a complex workflow whose steps run as actors over the
233
+ // A scenario is a complex workflow whose steps run as virtual users over the
233
234
  // real transport — same extraction rules as complex, distinct meta.
234
235
  wrapperType = 'scenario';
235
236
  }
@@ -243,7 +244,10 @@ export const addWorkflow = (logger, node, checker, state, options) => {
243
244
  const { pikkuFuncId, name, exportedName } = extractFunctionName(node, checker, state.rootDir);
244
245
  const workflowName = exportedName || name;
245
246
  if (!workflowName) {
246
- logger.critical(ErrorCode.MISSING_NAME, `Could not determine workflow name from export.`);
247
+ const sourceFile = node.getSourceFile();
248
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
249
+ logger.critical(ErrorCode.MISSING_NAME, `Could not determine workflow name from export at ${relative(state.rootDir, sourceFile.fileName)}:${line + 1}. ` +
250
+ `Assign the \`${expression.text}(...)\` call to an exported const; that name is how it is registered.`);
247
251
  return;
248
252
  }
249
253
  // Extract the function node (either direct function or from config.func)
@@ -358,11 +362,11 @@ export const addWorkflow = (logger, node, checker, state, options) => {
358
362
  const sink = steps.length > 0 ? [] : steps;
359
363
  getWorkflowInvocations(resolvedFunc, checker, state, workflowName, sink);
360
364
  }
361
- // Actor names the flow's steps run as (scenarios) — powers the console
362
- // personas view and the Scenario graph badges.
363
- const actorNames = [
365
+ // Virtual user names the flow's steps run as (scenarios) — powers the console
366
+ // virtual users view and the Scenario graph badges.
367
+ const virtualUserNames = [
364
368
  ...new Set(steps
365
- .map((s) => ('actor' in s ? s.actor : undefined))
369
+ .map((s) => ('virtualUser' in s ? s.virtualUser : undefined))
366
370
  .filter((a) => typeof a === 'string')),
367
371
  ];
368
372
  state.workflows.meta[workflowName] = {
@@ -380,7 +384,7 @@ export const addWorkflow = (logger, node, checker, state, options) => {
380
384
  skip,
381
385
  scenario: wrapperType === 'scenario' ? true : undefined,
382
386
  asserts,
383
- actors: actorNames.length > 0 ? actorNames : undefined,
387
+ virtualUsers: virtualUserNames.length > 0 ? virtualUserNames : undefined,
384
388
  };
385
389
  // The scenario "logger/config only" rule (PKU673) and the implicit
386
390
  // workflow platform-service injection both used to live here, reading
@@ -20,22 +20,28 @@ export declare enum ErrorCode {
20
20
  CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = "PKU672",
21
21
  SCENARIO_HAS_SERVICES = "PKU673",
22
22
  EXPECT_EVENTUALLY_SCENARIO_ONLY = "PKU675",
23
- SCENARIO_STEP_NEEDS_ACTOR = "PKU677",
23
+ SCENARIO_STEP_NEEDS_VIRTUAL_USER = "PKU677",
24
24
  SCENARIO_STEP_TARGET_NOT_STATIC = "PKU678",
25
25
  SCENARIO_NOT_EXTRACTABLE = "PKU679",
26
26
  SCENARIO_HAS_NO_ASSERTION = "PKU680",
27
- SCENARIO_STEP_PROSE_NAMES_ACTOR = "PKU681",
27
+ SCENARIO_STEP_PROSE_NAMES_VIRTUAL_USER = "PKU681",
28
28
  SCENARIO_HAS_NO_FEATURE = "PKU682",
29
29
  WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = "PKU600",
30
30
  INVALID_DSL_WORKFLOW = "PKU641",
31
31
  WORKFLOW_GRAPH_ADDON_NOT_WIRED = "PKU642",
32
32
  COMPLEX_WORKFLOW_NOT_ALLOWED = "PKU643",
33
+ SINGLE_STEP_WORKFLOW = "PKU644",
33
34
  REMOTE_ADDON_NOT_DEV_DEPENDENCY = "PKU338",
34
35
  REMOTE_ADDON_AUTH_UNRESOLVED = "PKU339",
35
36
  ADDON_NOT_INSTALLED = "PKU340",
36
37
  ADDON_MCP_FUNCTION_NOT_FOUND = "PKU341",
37
38
  ADDON_EXPOSE_FUNCTION_NOT_FOUND = "PKU343",
38
39
  ADDON_EXPOSE_NOT_STATIC = "PKU344",
40
+ ADDON_SCREENS_NOT_STATIC = "PKU345",
41
+ ADDON_UI_HAS_NO_SCREENS = "PKU346",
42
+ ADDON_USES_NOT_WIRED = "PKU347",
43
+ ADDON_NEEDS_NOT_MAPPED = "PKU348",
44
+ ADDON_CONTENT_GRANT_INVALID = "PKU349",
39
45
  DB_COLUMN_NAME_TYPE_CONTRADICTION = "PKU480",
40
46
  DB_JSON_COLUMN_UNTYPED = "PKU481",
41
47
  DB_FORMAT_ON_NON_STRING = "PKU482",
@@ -48,7 +54,7 @@ export declare enum ErrorCode {
48
54
  FUNCTION_METADATA_NOT_FOUND = "PKU559",
49
55
  DUPLICATE_AUTH_DEFINITION = "PKU581",
50
56
  AUTH_NOT_EXPORTED = "PKU582",
51
- DUPLICATE_PERSONAS_DEFINITION = "PKU585",
57
+ DUPLICATE_VIRTUAL_USERS_DEFINITION = "PKU585",
52
58
  ROUTE_PARAM_MISMATCH = "PKU571",
53
59
  ROUTE_QUERY_MISMATCH = "PKU572",
54
60
  AUTH_DISABLED_REQUIRES_SESSIONLESS = "PKU573",
@@ -22,16 +22,17 @@ export var ErrorCode;
22
22
  ErrorCode["CLI_CLIENTSIDE_RENDERER_HAS_SERVICES"] = "PKU672";
23
23
  ErrorCode["SCENARIO_HAS_SERVICES"] = "PKU673";
24
24
  ErrorCode["EXPECT_EVENTUALLY_SCENARIO_ONLY"] = "PKU675";
25
- ErrorCode["SCENARIO_STEP_NEEDS_ACTOR"] = "PKU677";
25
+ ErrorCode["SCENARIO_STEP_NEEDS_VIRTUAL_USER"] = "PKU677";
26
26
  ErrorCode["SCENARIO_STEP_TARGET_NOT_STATIC"] = "PKU678";
27
27
  ErrorCode["SCENARIO_NOT_EXTRACTABLE"] = "PKU679";
28
28
  ErrorCode["SCENARIO_HAS_NO_ASSERTION"] = "PKU680";
29
- ErrorCode["SCENARIO_STEP_PROSE_NAMES_ACTOR"] = "PKU681";
29
+ ErrorCode["SCENARIO_STEP_PROSE_NAMES_VIRTUAL_USER"] = "PKU681";
30
30
  ErrorCode["SCENARIO_HAS_NO_FEATURE"] = "PKU682";
31
31
  ErrorCode["WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED"] = "PKU600";
32
32
  ErrorCode["INVALID_DSL_WORKFLOW"] = "PKU641";
33
33
  ErrorCode["WORKFLOW_GRAPH_ADDON_NOT_WIRED"] = "PKU642";
34
34
  ErrorCode["COMPLEX_WORKFLOW_NOT_ALLOWED"] = "PKU643";
35
+ ErrorCode["SINGLE_STEP_WORKFLOW"] = "PKU644";
35
36
  // Remote addon (wireRemoteAddon) validation
36
37
  ErrorCode["REMOTE_ADDON_NOT_DEV_DEPENDENCY"] = "PKU338";
37
38
  ErrorCode["REMOTE_ADDON_AUTH_UNRESOLVED"] = "PKU339";
@@ -39,6 +40,11 @@ export var ErrorCode;
39
40
  ErrorCode["ADDON_MCP_FUNCTION_NOT_FOUND"] = "PKU341";
40
41
  ErrorCode["ADDON_EXPOSE_FUNCTION_NOT_FOUND"] = "PKU343";
41
42
  ErrorCode["ADDON_EXPOSE_NOT_STATIC"] = "PKU344";
43
+ ErrorCode["ADDON_SCREENS_NOT_STATIC"] = "PKU345";
44
+ ErrorCode["ADDON_UI_HAS_NO_SCREENS"] = "PKU346";
45
+ ErrorCode["ADDON_USES_NOT_WIRED"] = "PKU347";
46
+ ErrorCode["ADDON_NEEDS_NOT_MAPPED"] = "PKU348";
47
+ ErrorCode["ADDON_CONTENT_GRANT_INVALID"] = "PKU349";
42
48
  // Database schema codegen warnings
43
49
  ErrorCode["DB_COLUMN_NAME_TYPE_CONTRADICTION"] = "PKU480";
44
50
  ErrorCode["DB_JSON_COLUMN_UNTYPED"] = "PKU481";
@@ -60,7 +66,7 @@ export var ErrorCode;
60
66
  // scopes and roles held this rule until the generated `admin` tree made it
61
67
  // unsatisfiable, and get it back once `admin` is a default scope nobody
62
68
  // declares. Do not reuse those two numbers for anything else.
63
- ErrorCode["DUPLICATE_PERSONAS_DEFINITION"] = "PKU585";
69
+ ErrorCode["DUPLICATE_VIRTUAL_USERS_DEFINITION"] = "PKU585";
64
70
  // HTTP Route errors
65
71
  ErrorCode["ROUTE_PARAM_MISMATCH"] = "PKU571";
66
72
  ErrorCode["ROUTE_QUERY_MISMATCH"] = "PKU572";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { inspect, getInitialInspectorState } from './inspector.js';
2
2
  export type { TypesMap } from './types-map.js';
3
- export type { AddonConfig, AuthDefinition, CredentialOverrideMeta, InspectorDiagnostic, InspectorFilters, InspectorHTTPState, InspectorLogger, InspectorMiddlewareState, InspectorOptions, InspectorPermissionState, InspectorState, MetaInputTypes, MiddlewareGroupMeta, MiddlewareRegistration, SchemaRef, } from './types.js';
3
+ export type { AddonConfig, AuthDefinition, CredentialOverrideMeta, ScreensManifestMeta, AddonScreenMeta, InspectorDiagnostic, InspectorFilters, InspectorHTTPState, InspectorLogger, InspectorMiddlewareState, InspectorOptions, InspectorPermissionState, InspectorState, MetaInputTypes, MiddlewareGroupMeta, MiddlewareRegistration, SchemaRef, } from './types.js';
4
4
  export type { FilesAndMethodsErrors } from './utils/get-files-and-methods.js';
5
5
  export { ErrorCode } from './error-codes.js';
6
6
  export type { DiagnosticSeverity, CodedDiagnostic } from './error-codes.js';
package/dist/inspector.js CHANGED
@@ -9,9 +9,10 @@ import { findCommonAncestor } from './utils/find-root-dir.js';
9
9
  import { createSourceFileCacheHost } from './source-file-cache.js';
10
10
  import { createNestedProjectFilter } from './utils/nested-project-filter.js';
11
11
  import { toProgramPath } from './utils/program-path.js';
12
- import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateAgentScorerReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes, validatePersonaRoles, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, validateNoSecretAliasServices, registerDerivedOAuth2AppSecrets, validateSecretUsage, computeDiagnostics, validateSchemaReferences, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateScenarioFeatures, validateWorkflowGraphAddons, } from './utils/post-process.js';
12
+ import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateAgentScorerReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateAddonUses, validateAddonContentGrants, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes, validateVirtualUserRoles, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, validateNoSecretAliasServices, registerDerivedOAuth2AppSecrets, validateSecretUsage, computeDiagnostics, validateSchemaReferences, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateScenarioFeatures, validateWorkflowGraphAddons, } from './utils/post-process.js';
13
13
  import { validateExposedFunctionsGated } from './utils/validate-exposed-functions-gated.js';
14
14
  import { validateTagsResolveToMiddleware } from './utils/validate-tags-resolve-to-middleware.js';
15
+ import { validateSingleStepWorkflows } from './utils/validate-single-step-workflows.js';
15
16
  import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js';
16
17
  import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
17
18
  import { pikkuState } from '@pikku/core/state';
@@ -169,7 +170,7 @@ export function getInitialInspectorState(rootDir) {
169
170
  definitions: [],
170
171
  files: new Set(),
171
172
  },
172
- personas: {
173
+ virtualUsers: {
173
174
  definitions: [],
174
175
  files: new Set(),
175
176
  },
@@ -408,6 +409,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
408
409
  computeDiagnostics(state);
409
410
  validateSchemaWiringSeparation(logger, state);
410
411
  validateWorkflowGraphAddons(logger, state);
412
+ validateSingleStepWorkflows(logger, state);
411
413
  validateScenarioServices(logger, state);
412
414
  validateScenarioSteps(logger, state);
413
415
  validateScenarioFeatures(logger, state);
@@ -427,12 +429,14 @@ export const inspect = async (logger, routeFiles, options = {}) => {
427
429
  validateSecretOverrides(logger, state);
428
430
  validateVariableOverrides(logger, state);
429
431
  validateCredentialOverrides(logger, state);
432
+ validateAddonUses(logger, state);
433
+ validateAddonContentGrants(logger, state);
430
434
  validateRemoteAddonDependencies(logger, state);
431
435
  validateRemoteAddonAuth(logger, state);
432
436
  applyDeclaredScopes(state);
433
437
  validateScopeReferences(logger, state);
434
438
  validateSystemRoleScopes(logger, state);
435
- validatePersonaRoles(logger, state);
439
+ validateVirtualUserRoles(logger, state);
436
440
  logger.endValidationPass?.();
437
441
  }
438
442
  const cpu = process.cpuUsage(cpuStart);
package/dist/types.d.ts CHANGED
@@ -19,7 +19,7 @@ import type { CredentialDefinitions } from '@pikku/core/credential';
19
19
  import type { ScopeDefinitions } from '@pikku/core/scope';
20
20
  import type { SystemRoleDefinitions } from '@pikku/core/role';
21
21
  import type { FeatureFlagDefinitions } from '@pikku/core/flag';
22
- import type { PersonaDefinitions } from '@pikku/core/persona';
22
+ import type { VirtualUserDefinitions } from '@pikku/core/virtual-user';
23
23
  import type { VariableDefinitions } from '@pikku/core/variable';
24
24
  import type { TypesMap } from './types-map.js';
25
25
  import type { FunctionsMeta } from '@pikku/core/services';
@@ -491,7 +491,38 @@ export type CredentialOverrideMeta = string | {
491
491
  name?: string;
492
492
  mode?: 'singleton' | 'wire';
493
493
  };
494
+ /** A screen of an addon as `defineScreens` declares it, read statically. */
495
+ export type AddonScreenMeta = {
496
+ path: string;
497
+ title: string;
498
+ nav?: boolean;
499
+ scopes?: string[];
500
+ } & ({
501
+ component: string;
502
+ app?: undefined;
503
+ } | {
504
+ app: string;
505
+ component?: undefined;
506
+ });
507
+ export type ScreensManifestMeta = {
508
+ title: string;
509
+ icon?: string;
510
+ /** The source file whose `defineScreens` call declared this manifest. */
511
+ file: string;
512
+ screens: AddonScreenMeta[];
513
+ /**
514
+ * The addon's role: every scope its own functions require, derived by
515
+ * `pikku all` and never declared. Absent from the raw `defineScreens` read.
516
+ * On a wired instance it also carries the host's `wireAddon` scopes, which
517
+ * the runner demands of every function in the package.
518
+ */
519
+ scopes?: string[];
520
+ };
494
521
  export interface InspectorState {
522
+ /** Set when this package calls `defineScreens`: the screens it ships. */
523
+ screensManifest?: ScreensManifestMeta;
524
+ /** The screens of the addons this project wires with `ui: true`, by namespace. */
525
+ addonScreens?: Record<string, ScreensManifestMeta>;
495
526
  rootDir: string;
496
527
  singletonServicesTypeImportMap: PathToNameAndType;
497
528
  wireServicesTypeImportMap: PathToNameAndType;
@@ -605,7 +636,11 @@ export interface InspectorState {
605
636
  package: string;
606
637
  /** The app source file whose `wireAddon` call declared this instance. */
607
638
  file?: string;
639
+ /** Declared with `ui: true`: the package must ship screens, and the host mounts them. */
640
+ ui?: boolean;
608
641
  rpcEndpoint?: string;
642
+ /** The other addons this instance may call: package -> the name it is wired under. */
643
+ uses?: Record<string, string>;
609
644
  /**
610
645
  * `true` offers every function the addon declared `mcp: true`; a list
611
646
  * names the functions to offer, whatever the addon declared. Absent
@@ -660,6 +695,13 @@ export interface InspectorState {
660
695
  * `CredentialService` instead of one scoped to its declared credentials.
661
696
  */
662
697
  globalCredentials?: string;
698
+ contentBucket?: string;
699
+ contentGrants?: Record<string, string>;
700
+ /**
701
+ * The app's stated reason for handing this instance the whole
702
+ * `ContentService` instead of one scoped to its own folder.
703
+ */
704
+ globalContent?: string;
663
705
  }>;
664
706
  wireAddonFiles: Set<string>;
665
707
  };
@@ -742,8 +784,8 @@ export interface InspectorState {
742
784
  definitions: FeatureFlagDefinitions;
743
785
  files: Set<string>;
744
786
  };
745
- personas: {
746
- definitions: PersonaDefinitions;
787
+ virtualUsers: {
788
+ definitions: VirtualUserDefinitions;
747
789
  files: Set<string>;
748
790
  };
749
791
  variables: {
@@ -30,7 +30,7 @@ export function funcIdToTypeName(id) {
30
30
  }
31
31
  /**
32
32
  * The step wrappers, whichever kind of subject acts in them. A scenario calls a
33
- * platform or addon step by name exactly as it calls a persona one.
33
+ * platform or addon step by name exactly as it calls a virtual user one.
34
34
  */
35
35
  const SCENARIO_STEP_WRAPPERS = new Set([
36
36
  'pikkuScenarioStep',
@@ -102,7 +102,7 @@ export const assertStringLiteralProperty = (obj, propertyName, wiringType, logge
102
102
  * that anything written as an expression comes through raw: a `description`
103
103
  * split across lines as `'Fetch the thing ' + 'from upstream'` reaches
104
104
  * `infra.json` with the quotes and the `+` still in it, and is rendered that
105
- * way in the fabric console.
105
+ * way in the cloud console.
106
106
  *
107
107
  * Deliberately checker-free, unlike `extractStringLiteral`. Every value this
108
108
  * resolves is a literal in the same expression, so no symbol resolution is
@@ -8,11 +8,6 @@ export declare const describeMissingAddonMeta: (namespace: string, packageName:
8
8
  * Every source starts off: the app turns one on at runtime.
9
9
  */
10
10
  export declare const namespaceAddonWebhookSources: (sources: WebhookSourcesMeta, namespace: string) => WebhookSourcesMeta;
11
- /**
12
- * After the setup sweep discovers wireAddon() declarations, load each addon
13
- * package's function metadata so that wiring handlers (channels, HTTP routes,
14
- * schedules, etc.) can look up addon function types during the routes sweep.
15
- */
16
11
  export declare function loadAddonFunctionsMeta(logger: InspectorLogger, state: InspectorState): Promise<void>;
17
12
  /**
18
13
  * Load addon schemas into state.schemas. Called after generateAllSchemas
@@ -45,6 +45,7 @@ const applyPackageToCLICommands = (commands, packageName, namespace) => {
45
45
  }
46
46
  if (namespace &&
47
47
  typeof command.pikkuFuncId === 'string' &&
48
+ command.pikkuFuncId !== '' &&
48
49
  !command.pikkuFuncId.includes(':')) {
49
50
  command.pikkuFuncId = `${namespace}:${command.pikkuFuncId}`;
50
51
  }
@@ -181,6 +182,27 @@ export const namespaceAddonWebhookSources = (sources, namespace) => {
181
182
  * package's function metadata so that wiring handlers (channels, HTTP routes,
182
183
  * schedules, etc.) can look up addon function types during the routes sweep.
183
184
  */
185
+ const loadScreensManifest = async (require, packageName) => {
186
+ const path = resolveAddonMeta(require, packageName, 'screens/pikku-screens-meta');
187
+ if (!path)
188
+ return null;
189
+ return JSON.parse(await readFile(path, 'utf-8'));
190
+ };
191
+ const readAddonNeeds = async (dirs, packageName) => {
192
+ const dir = findInstalledPackageDir(dirs, packageName);
193
+ if (!dir)
194
+ return [];
195
+ try {
196
+ const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf-8'));
197
+ const uses = pkg?.pikku?.uses;
198
+ return Array.isArray(uses)
199
+ ? uses.filter((u) => typeof u === 'string')
200
+ : [];
201
+ }
202
+ catch {
203
+ return [];
204
+ }
205
+ };
184
206
  export async function loadAddonFunctionsMeta(logger, state) {
185
207
  const { wireAddonDeclarations } = state.rpc;
186
208
  if (wireAddonDeclarations.size === 0)
@@ -194,6 +216,37 @@ export async function loadAddonFunctionsMeta(logger, state) {
194
216
  const dirs = addonResolutionDirs(state.rootDir, decl.file);
195
217
  const require = createAddonResolver(dirs);
196
218
  try {
219
+ for (const needed of await readAddonNeeds(dirs, decl.package)) {
220
+ if (!decl.uses?.[needed]) {
221
+ logger.critical(ErrorCode.ADDON_NEEDS_NOT_MAPPED, `${decl.package} needs ${needed}, but wireAddon('${namespace}') does not map it. Wire ${needed} and add uses: { '${needed}': '<name>' } to this wireAddon.`);
222
+ }
223
+ }
224
+ if (decl.ui) {
225
+ let manifest;
226
+ let unreadable = false;
227
+ try {
228
+ manifest = await loadScreensManifest(require, decl.package);
229
+ }
230
+ catch (e) {
231
+ logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but the screens manifest of ${decl.package} could not be read: ${e instanceof Error ? e.message : e}`);
232
+ unreadable = true;
233
+ }
234
+ if (unreadable) {
235
+ // reported above; the addon's functions and scopes still load
236
+ }
237
+ else if (!manifest || manifest.screens.length === 0) {
238
+ logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but ${decl.package} declares no screens. Add a defineScreens call to the package, or drop ui.`);
239
+ }
240
+ else {
241
+ ;
242
+ (state.addonScreens ??= {})[namespace] = {
243
+ ...manifest,
244
+ scopes: [
245
+ ...new Set([...(manifest.scopes ?? []), ...(decl.scopes ?? [])]),
246
+ ].sort(),
247
+ };
248
+ }
249
+ }
197
250
  // Verbose first. `description` is one of the fields stripped from the
198
251
  // minimal copy, and it is what an addon's function is offered to a model
199
252
  // under — both as an MCP tool below and as an agent tool. Read the minimal
@@ -1,7 +1,7 @@
1
- import type { InspectorState, InspectorLogger, InspectorOptions } from "../types.js";
2
- import type { PermissionMetadata } from "@pikku/core/function";
3
- import type { MiddlewareMetadata } from "@pikku/core/middleware";
4
- import type ts from "typescript";
1
+ import type { InspectorState, InspectorLogger, InspectorOptions } from '../types.js';
2
+ import type { PermissionMetadata } from '@pikku/core/function';
3
+ import type { MiddlewareMetadata } from '@pikku/core/middleware';
4
+ import type ts from 'typescript';
5
5
  /**
6
6
  * Stamp the inspected authorize/callbacks service set onto the generated auth
7
7
  * handler's function meta.
@@ -15,7 +15,7 @@ import type ts from "typescript";
15
15
  * onto the handler meta. Re-derived every inspect and ordered BEFORE
16
16
  * `aggregateRequiredServices` so it flows into `requiredServices`.
17
17
  */
18
- export declare function stampAuthHandlerServices(state: InspectorState | Omit<InspectorState, "typesLookup">): void;
18
+ export declare function stampAuthHandlerServices(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
19
19
  /**
20
20
  * Helper to extract wire-level middleware/permission names from metadata.
21
21
  * Only extracts type:'wire' variants (individual middleware/permissions).
@@ -29,10 +29,12 @@ export declare function extractWireNames(list?: Array<MiddlewareMetadata | Permi
29
29
  * Note: usedFunctions, usedMiddleware, and usedPermissions are tracked directly
30
30
  * in the add-* methods during AST traversal for efficiency.
31
31
  */
32
- export declare function aggregateRequiredServices(state: InspectorState | Omit<InspectorState, "typesLookup">): void;
33
- export declare function validateSecretOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
34
- export declare function validateCredentialOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
35
- export declare function validateVariableOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
32
+ export declare function aggregateRequiredServices(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
33
+ export declare function validateSecretOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
34
+ export declare function validateAddonUses(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
35
+ export declare function validateAddonContentGrants(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
36
+ export declare function validateCredentialOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
37
+ export declare function validateVariableOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
36
38
  /**
37
39
  * Every wired addon must be installed, in the bucket matching how it is used.
38
40
  *
@@ -56,14 +58,14 @@ export declare function validateVariableOverrides(logger: InspectorLogger, state
56
58
  * up in `dependencies`. Asking the resolver is the only question that matches
57
59
  * what the loader will actually do.
58
60
  */
59
- export declare function validateRemoteAddonDependencies(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
61
+ export declare function validateRemoteAddonDependencies(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
60
62
  /**
61
63
  * The auth a `wireRemoteAddon` consumer binds must reference a slot the consumer
62
64
  * actually declares: a `credentialId` must be a wired credential, a `secretId`
63
65
  * a wired secret. A custom `resolve()` and a public (omitted) surface are not
64
66
  * statically checkable and are left to runtime.
65
67
  */
66
- export declare function validateRemoteAddonAuth(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
68
+ export declare function validateRemoteAddonAuth(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
67
69
  export declare function computeResolvedIOTypes(state: InspectorState): void;
68
70
  export declare function computeMiddlewareGroupsMeta(state: InspectorState): void;
69
71
  export declare function computePermissionsGroupsMeta(state: InspectorState): void;
@@ -88,11 +90,11 @@ export declare function validateSchemaReferences(logger: InspectorLogger, state:
88
90
  * A model is either concrete (`provider/model`) or an alias, which must appear
89
91
  * in the `models` table from pikku.config.json.
90
92
  */
91
- export declare function validateAgentModels(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">, modelAliases?: Record<string, string>): void;
93
+ export declare function validateAgentModels(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, modelAliases?: Record<string, string>): void;
92
94
  /**
93
95
  * Scenarios are pure stories of remote RPCs (same rule as client-side CLI
94
96
  * renderers): the func may only destructure logger/config — everything else
95
- * must go through actor steps so the flow runs against the TARGET
97
+ * must go through virtual user steps so the flow runs against the TARGET
96
98
  * environment, never local services.
97
99
  *
98
100
  * This runs in post-processing rather than in `addWorkflow` because
@@ -100,17 +102,17 @@ export declare function validateAgentModels(logger: InspectorLogger, state: Insp
100
102
  * `addFunctions` (`visitFunctions`) has populated `state.functions.meta` — so
101
103
  * the check read `undefined` there and never fired.
102
104
  */
103
- export declare function validateScenarioServices(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
105
+ export declare function validateScenarioServices(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
104
106
  /**
105
- * Does this step's prose open by naming the actor who is already its subject?
107
+ * Does this step's prose open by naming the virtual user who is already its subject?
106
108
  *
107
- * `composeStepProse` renders the actor as the sentence's subject, so a step
109
+ * `composeStepProse` renders the virtual user as the sentence's subject, so a step
108
110
  * authored as `'sam' creates the client` reads "Given sam 'sam' creates the
109
- * client". Only the subject position counts: a persona keyed after a role noun
111
+ * client". Only the subject position counts: a virtual user keyed after a role noun
110
112
  * ("admin") must still be able to say "creates the admin client", and naming
111
113
  * someone mid-sentence — "sends sam an invite" — is ordinary prose.
112
114
  */
113
- export declare const proseOpensWithActor: (prose: string, actor: string) => boolean;
115
+ export declare const proseOpensWithVirtualUser: (prose: string, virtualUser: string) => boolean;
114
116
  /**
115
117
  * Walk every scenario step, wherever it is nested, and validate the two things
116
118
  * that can only be known once both the workflow meta and the function meta
@@ -118,9 +120,9 @@ export declare const proseOpensWithActor: (prose: string, actor: string) => bool
118
120
  *
119
121
  * - the step target must be a static string literal (PKU678) — otherwise it
120
122
  * can't be bundled, typed or drawn;
121
- * - a step that runs as a persona must be given one (PKU677) — declared by
122
- * `actor: true`, or implied by a `browser` binding, since the browser context
123
- * signs in as that persona. It is checked statically even though a browser
123
+ * - a step that runs as a virtual user must be given one (PKU677) — declared by
124
+ * `virtualUser: true`, or implied by a `browser` binding, since the browser context
125
+ * signs in as that virtual user. It is checked statically even though a browser
124
126
  * binding only runs on a browser run: a step that can never be driven by a
125
127
  * human is a latent failure, not a passing default run.
126
128
  * - a scenario written as a step ladder must assert something (PKU680).
@@ -128,7 +130,7 @@ export declare const proseOpensWithActor: (prose: string, actor: string) => bool
128
130
  * It also marks the step's function as used, so `scenario run` boots the
129
131
  * singletons the step actually needs.
130
132
  */
131
- export declare function validateScenarioSteps(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
133
+ export declare function validateScenarioSteps(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
132
134
  /**
133
135
  * Reports scenarios that no `pikkuFeature` lists.
134
136
  *
@@ -153,7 +155,7 @@ export declare function validateScenarioSteps(logger: InspectorLogger, state: In
153
155
  * the check reports it then and downgrades to a warn naming both sides, rather
154
156
  * than accusing a scenario that may well be in that array.
155
157
  */
156
- export declare function validateScenarioFeatures(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
158
+ export declare function validateScenarioFeatures(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
157
159
  /**
158
160
  * A `pikkuWorkflowGraph` node that references the `graph:` namespace (e.g.
159
161
  * `graph:editFields`) needs @pikku/addon-graph wired — otherwise the RPC never
@@ -178,7 +180,7 @@ export declare function validateSchemaWiringSeparation(logger: InspectorLogger,
178
180
  * a name the omit does not cover. A name-based rule misses this; the type does
179
181
  * not.
180
182
  */
181
- export declare function validateNoSecretAliasServices(logger: InspectorLogger, checker: ts.TypeChecker, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
183
+ export declare function validateNoSecretAliasServices(logger: InspectorLogger, checker: ts.TypeChecker, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
182
184
  /**
183
185
  * Reports secret reads the catalogue does not account for.
184
186
  *
@@ -191,8 +193,8 @@ export declare function validateNoSecretAliasServices(logger: InspectorLogger, c
191
193
  * deployment is asked for the client id and secret every connect flow needs
192
194
  * without an author restating a shape the runtime already fixes.
193
195
  */
194
- export declare function registerDerivedOAuth2AppSecrets(state: InspectorState | Omit<InspectorState, "typesLookup">): void;
195
- export declare function validateSecretUsage(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
196
+ export declare function registerDerivedOAuth2AppSecrets(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
197
+ export declare function validateSecretUsage(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
196
198
  export declare function computeDiagnostics(state: InspectorState): void;
197
199
  /**
198
200
  * Validates that every scope referenced by a function is declared via
@@ -201,7 +203,7 @@ export declare function computeDiagnostics(state: InspectorState): void;
201
203
  * A `*` suffix is a wildcard requirement: `admin:*` requires the `admin` scope
202
204
  * to be declared, and grants its whole subtree.
203
205
  */
204
- export declare function validateScopeReferences(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
206
+ export declare function validateScopeReferences(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
205
207
  /**
206
208
  * Resolves every `tools:` entry on every agent and reports the ones that do not
207
209
  * land on a real function, or that land on one with nothing to tell the model.
@@ -218,7 +220,7 @@ export declare function validateScopeReferences(logger: InspectorLogger, state:
218
220
  * quality loss rather than a failure. A title deliberately does not satisfy it —
219
221
  * a title labels a tool in a UI, it does not tell a model when to reach for it.
220
222
  */
221
- export declare function validateAgentToolReferences(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">, options?: InspectorOptions): void;
223
+ export declare function validateAgentToolReferences(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, options?: InspectorOptions): void;
222
224
  /**
223
225
  * Validates that every scorer an agent asks to be graded by exists.
224
226
  *
@@ -226,7 +228,7 @@ export declare function validateAgentToolReferences(logger: InspectorLogger, sta
226
228
  * that has already answered the user — which means a typo would otherwise cost
227
229
  * nothing visible and simply grade nothing, forever.
228
230
  */
229
- export declare function validateAgentScorerReferences(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
231
+ export declare function validateAgentScorerReferences(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
230
232
  /**
231
233
  * Validates that every scope granted by a system role is declared via
232
234
  * `defineScope`. Runs after all visitors, so declaration order does not matter.
@@ -234,21 +236,21 @@ export declare function validateAgentScorerReferences(logger: InspectorLogger, s
234
236
  * The same rule functions get, for a sharper reason: a role granting an
235
237
  * undeclared scope is not merely useless, it is *invisibly* useless. No
236
238
  * function checks the scope, so the role appears to work — every call a
237
- * persona holding it makes succeeds or fails for unrelated reasons, and the
239
+ * virtual user holding it makes succeeds or fails for unrelated reasons, and the
238
240
  * authorization boundary it was written to describe is never exercised.
239
241
  *
240
242
  * A `*` suffix grants a subtree; the node it hangs off must exist.
241
243
  */
242
- export declare function validateSystemRoleScopes(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
244
+ export declare function validateSystemRoleScopes(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
243
245
  /**
244
- * Validates that every role a persona names is declared with
246
+ * Validates that every role a virtual user names is declared with
245
247
  * `defineSystemRole`. Runs after all visitors, so declaration order does not
246
248
  * matter.
247
249
  *
248
250
  * Custom roles are refused rather than tolerated. A role composed in the
249
251
  * console does not exist at build time and can be deleted after it, so a
250
- * persona pinned to one silently stops testing what it claims to — and the
252
+ * virtual user pinned to one silently stops testing what it claims to — and the
251
253
  * symptom is a scatter of 403s that read like authorization findings rather
252
254
  * than like a deleted role.
253
255
  */
254
- export declare function validatePersonaRoles(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
256
+ export declare function validateVirtualUserRoles(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;