@pikku/core 0.12.80 → 0.12.82

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 (231) hide show
  1. package/CHANGELOG.md +312 -0
  2. package/dist/errors/index.d.ts +1 -1
  3. package/dist/errors/index.js +1 -1
  4. package/dist/function/function-runner.js +2 -5
  5. package/dist/function/index.d.ts +1 -1
  6. package/dist/index.d.ts +11 -11
  7. package/dist/index.js +3 -3
  8. package/dist/pikku-state.js +4 -0
  9. package/dist/services/ai-agent-runner-service.d.ts +7 -0
  10. package/dist/services/ai-run-state-service.d.ts +10 -0
  11. package/dist/services/in-memory-ai-run-state-service.d.ts +5 -1
  12. package/dist/services/in-memory-ai-run-state-service.js +9 -0
  13. package/dist/services/index.d.ts +15 -15
  14. package/dist/services/index.js +5 -5
  15. package/dist/services/scoped-credential-service.d.ts +21 -0
  16. package/dist/services/scoped-credential-service.js +53 -0
  17. package/dist/testing/service-tests/ai-storage-service-tests.js +76 -0
  18. package/dist/types/core.types.d.ts +0 -2
  19. package/dist/types/state.types.d.ts +13 -0
  20. package/dist/wirings/actor-flow/index.d.ts +1 -1
  21. package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +58 -0
  22. package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
  23. package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
  24. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
  25. package/dist/wirings/ai-agent/ai-agent-memory.js +34 -17
  26. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
  27. package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
  28. package/dist/wirings/ai-agent/ai-agent-prepare.js +2 -0
  29. package/dist/wirings/ai-agent/ai-agent-runner.js +61 -40
  30. package/dist/wirings/ai-agent/ai-agent-stream.js +89 -36
  31. package/dist/wirings/ai-agent/ai-agent-turn.d.ts +1 -0
  32. package/dist/wirings/ai-agent/ai-agent-turn.js +1 -0
  33. package/dist/wirings/ai-agent/ai-agent.types.d.ts +46 -1
  34. package/dist/wirings/ai-agent/index.d.ts +8 -7
  35. package/dist/wirings/ai-agent/index.js +5 -4
  36. package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
  37. package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
  38. package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
  39. package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
  40. package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
  41. package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
  42. package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
  43. package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
  44. package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
  45. package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
  46. package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
  47. package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
  48. package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
  49. package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
  50. package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
  51. package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
  52. package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
  53. package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
  54. package/dist/wirings/ai-scorer/index.d.ts +6 -0
  55. package/dist/wirings/ai-scorer/index.js +5 -0
  56. package/dist/wirings/channel/index.d.ts +5 -6
  57. package/dist/wirings/channel/index.js +3 -4
  58. package/dist/wirings/channel/local/local-channel-runner.js +8 -1
  59. package/dist/wirings/cli/channel/cli-raw-channel-runner.js +9 -1
  60. package/dist/wirings/cli/channel/index.d.ts +1 -2
  61. package/dist/wirings/cli/channel/index.js +0 -1
  62. package/dist/wirings/cli/cli-runner.js +13 -1
  63. package/dist/wirings/credential/index.d.ts +1 -1
  64. package/dist/wirings/gateway/index.d.ts +1 -1
  65. package/dist/wirings/http/http-runner.js +8 -2
  66. package/dist/wirings/http/index.d.ts +1 -2
  67. package/dist/wirings/mcp/index.d.ts +1 -1
  68. package/dist/wirings/mcp/mcp-runner.d.ts +15 -0
  69. package/dist/wirings/mcp/mcp-runner.js +18 -5
  70. package/dist/wirings/persona/index.d.ts +3 -4
  71. package/dist/wirings/persona/index.js +2 -3
  72. package/dist/wirings/queue/index.d.ts +1 -3
  73. package/dist/wirings/queue/index.js +1 -3
  74. package/dist/wirings/rpc/addon-runner.d.ts +4 -0
  75. package/dist/wirings/rpc/addon-runner.js +19 -3
  76. package/dist/wirings/rpc/rpc-runner.js +2 -0
  77. package/dist/wirings/rpc/rpc-types.d.ts +4 -0
  78. package/dist/wirings/rpc/wire-addon.d.ts +13 -0
  79. package/dist/wirings/rpc/wire-addon.js +4 -0
  80. package/dist/wirings/scheduler/index.d.ts +1 -1
  81. package/dist/wirings/trigger/index.d.ts +1 -1
  82. package/dist/wirings/virtual-user/index.d.ts +5 -6
  83. package/dist/wirings/virtual-user/index.js +2 -4
  84. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +85 -15
  85. package/dist/wirings/workflow/index.d.ts +6 -6
  86. package/dist/wirings/workflow/index.js +2 -2
  87. package/dist/wirings/workflow/pikku-scenario-service.d.ts +7 -7
  88. package/dist/wirings/workflow/pikku-scenario-service.js +39 -13
  89. package/dist/wirings/workflow/pikku-workflow-service.js +17 -3
  90. package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
  91. package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
  92. package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
  93. package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
  94. package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
  95. package/dist/wirings/workflow/workflow-approval.d.ts +29 -1
  96. package/dist/wirings/workflow/workflow-approval.js +65 -2
  97. package/dist/wirings/workflow/workflow-run-ownership.d.ts +2 -1
  98. package/dist/wirings/workflow/workflow-run-ownership.js +2 -1
  99. package/dist/wirings/workflow/workflow.types.d.ts +1 -1
  100. package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -0
  101. package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -0
  102. package/knowledge/decisions/internals/index.md +30 -3
  103. package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +115 -0
  104. package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +37 -0
  105. package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +30 -14
  106. package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -0
  107. package/knowledge/decisions/security/index.md +3 -1
  108. package/knowledge/questions/index.md +1 -1
  109. package/package.json +3 -1
  110. package/scripts/generate-api-report.mts +143 -18
  111. package/src/api-report.test.ts +2 -2
  112. package/src/errors/index.ts +1 -1
  113. package/src/function/function-runner.test.ts +52 -0
  114. package/src/function/function-runner.ts +5 -9
  115. package/src/function/index.ts +0 -2
  116. package/src/index.ts +0 -35
  117. package/src/pikku-state.ts +5 -0
  118. package/src/public-surface.json +70 -94
  119. package/src/services/ai-agent-runner-service.ts +12 -1
  120. package/src/services/ai-run-state-service.ts +11 -0
  121. package/src/services/in-memory-ai-run-state-service.ts +13 -0
  122. package/src/services/index.ts +3 -43
  123. package/src/services/scoped-credential-service.test.ts +86 -0
  124. package/src/services/scoped-credential-service.ts +63 -0
  125. package/src/testing/service-tests/ai-storage-service-tests.ts +93 -0
  126. package/src/types/core.types.ts +3 -6
  127. package/src/types/state.types.ts +16 -0
  128. package/src/wirings/actor-flow/index.ts +0 -3
  129. package/src/wirings/ai-agent/ai-agent-finalize.test.ts +186 -0
  130. package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
  131. package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
  132. package/src/wirings/ai-agent/ai-agent-memory.ts +54 -38
  133. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
  134. package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
  135. package/src/wirings/ai-agent/ai-agent-prepare.ts +2 -0
  136. package/src/wirings/ai-agent/ai-agent-runner.ts +71 -40
  137. package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
  138. package/src/wirings/ai-agent/ai-agent-stream.ts +116 -54
  139. package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
  140. package/src/wirings/ai-agent/ai-agent-turn.ts +1 -0
  141. package/src/wirings/ai-agent/ai-agent.types.ts +64 -4
  142. package/src/wirings/ai-agent/index.ts +2 -16
  143. package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
  144. package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
  145. package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
  146. package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
  147. package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
  148. package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
  149. package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
  150. package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
  151. package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
  152. package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
  153. package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
  154. package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
  155. package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
  156. package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
  157. package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
  158. package/src/wirings/ai-scorer/index.ts +24 -0
  159. package/src/wirings/channel/index.ts +1 -20
  160. package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
  161. package/src/wirings/channel/local/local-channel-runner.ts +8 -1
  162. package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +23 -0
  163. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
  164. package/src/wirings/cli/channel/index.ts +0 -7
  165. package/src/wirings/cli/cli-runner.test.ts +68 -0
  166. package/src/wirings/cli/cli-runner.ts +18 -1
  167. package/src/wirings/credential/index.ts +0 -1
  168. package/src/wirings/gateway/index.ts +0 -3
  169. package/src/wirings/http/http-runner.test.ts +66 -0
  170. package/src/wirings/http/http-runner.ts +10 -2
  171. package/src/wirings/http/index.ts +1 -1
  172. package/src/wirings/mcp/index.ts +0 -1
  173. package/src/wirings/mcp/mcp-runner.test.ts +181 -0
  174. package/src/wirings/mcp/mcp-runner.ts +35 -5
  175. package/src/wirings/persona/index.ts +0 -8
  176. package/src/wirings/queue/index.ts +0 -14
  177. package/src/wirings/rpc/addon-runner.ts +34 -3
  178. package/src/wirings/rpc/addon-secrets.test.ts +261 -0
  179. package/src/wirings/rpc/rpc-runner.test.ts +2 -0
  180. package/src/wirings/rpc/rpc-runner.ts +2 -0
  181. package/src/wirings/rpc/rpc-types.ts +4 -0
  182. package/src/wirings/rpc/wire-addon.ts +17 -0
  183. package/src/wirings/scheduler/index.ts +0 -1
  184. package/src/wirings/trigger/index.ts +0 -1
  185. package/src/wirings/virtual-user/index.ts +0 -16
  186. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +96 -16
  187. package/src/wirings/workflow/graph/graph-runner.test.ts +72 -0
  188. package/src/wirings/workflow/index.ts +2 -20
  189. package/src/wirings/workflow/pikku-scenario-service.ts +60 -15
  190. package/src/wirings/workflow/pikku-workflow-service.test.ts +13 -12
  191. package/src/wirings/workflow/pikku-workflow-service.ts +28 -4
  192. package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
  193. package/src/wirings/workflow/scenario-hooks.test.ts +3 -2
  194. package/src/wirings/workflow/scenario-step.types.ts +8 -0
  195. package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
  196. package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
  197. package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
  198. package/src/wirings/workflow/workflow-approval.ts +113 -9
  199. package/src/wirings/workflow/workflow-run-authority.test.ts +12 -15
  200. package/src/wirings/workflow/workflow-run-ownership.ts +2 -1
  201. package/src/wirings/workflow/workflow.types.ts +0 -9
  202. package/src/wirings-stay-decoupled.test.ts +6 -2
  203. package/tsconfig.tsbuildinfo +1 -1
  204. package/dist/internal.d.ts +0 -3
  205. package/dist/internal.js +0 -2
  206. package/dist/middleware/timeout.d.ts +0 -9
  207. package/dist/middleware/timeout.js +0 -15
  208. package/dist/pikku-response.d.ts +0 -6
  209. package/dist/pikku-response.js +0 -6
  210. package/dist/services/gopass-secrets.d.ts +0 -15
  211. package/dist/services/gopass-secrets.js +0 -76
  212. package/dist/services/http-scenario-actors.d.ts +0 -75
  213. package/dist/services/http-scenario-actors.js +0 -195
  214. package/dist/services/http-user-flow-actors.d.ts +0 -67
  215. package/dist/services/http-user-flow-actors.js +0 -193
  216. package/dist/services/scenario-actors-service.d.ts +0 -127
  217. package/dist/services/scenario-actors-service.js +0 -40
  218. package/dist/services/user-flow-actors-service.d.ts +0 -39
  219. package/dist/services/user-flow-actors-service.js +0 -1
  220. package/dist/wirings/credential/wire-credential.d.ts +0 -48
  221. package/dist/wirings/credential/wire-credential.js +0 -47
  222. package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
  223. package/dist/wirings/oauth2/oauth2-client.js +0 -263
  224. package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
  225. package/dist/wirings/oauth2/oauth2-routes.js +0 -146
  226. package/dist/wirings/scope/wire-scope.d.ts +0 -33
  227. package/dist/wirings/scope/wire-scope.js +0 -32
  228. package/dist/wirings/workflow/dsl/index.d.ts +0 -5
  229. package/dist/wirings/workflow/dsl/index.js +0 -4
  230. package/dist/wirings/workflow/graph/index.d.ts +0 -5
  231. package/dist/wirings/workflow/graph/index.js +0 -4
@@ -1,4 +1,3 @@
1
1
  export { executeCLIViaChannel } from './cli-channel-runner.js';
2
2
  export { handleRawCLI } from './cli-raw-channel-runner.js';
3
3
  export { executeRawCLIViaChannel } from './cli-raw-client-runner.js';
4
- export { APPROVAL_FLAGS, approverForMode, createTerminalApprover, takeApprovalFlags, } from './cli-approval.js';
@@ -87,7 +87,19 @@ function registerCLICommands(commands, path = [], inheritedOptions = {}, program
87
87
  programs[program].commandMiddleware[commandId] = command.middleware;
88
88
  }
89
89
  }
90
- addFunction(funcName, unwrapFunc(command), currentMeta?.packageName);
90
+ // Merge the command-level auth/permissions into the registered config so
91
+ // they are actually enforced by the function runner. They were accepted by
92
+ // the types but dropped here, making a command's declared access control a
93
+ // silent no-op. Command-level wins over the handler's own, then falls back
94
+ // to it.
95
+ const unwrapped = unwrapFunc(command);
96
+ const commandAuth = typeof command === 'object' ? command.auth : undefined;
97
+ const commandPermissions = typeof command === 'object' ? command.permissions : undefined;
98
+ addFunction(funcName, {
99
+ ...unwrapped,
100
+ auth: commandAuth ?? unwrapped.auth,
101
+ permissions: commandPermissions ?? unwrapped.permissions,
102
+ }, currentMeta?.packageName);
91
103
  if (typeof command === 'object' && command.render) {
92
104
  if (programs[program]) {
93
105
  programs[program].renderers[commandId] = command.render;
@@ -1,3 +1,3 @@
1
1
  export { defineCredential } from './define-credential.js';
2
2
  export { validateAndBuildCredentialDefinitionsMeta } from './validate-credential-definitions.js';
3
- export type { CoreCredential, CredentialDefinitionMeta, CredentialDefinitionsMeta, CredentialDefinitions, } from './credential.types.js';
3
+ export type { CoreCredential, CredentialDefinitionsMeta, CredentialDefinitions, } from './credential.types.js';
@@ -1,2 +1,2 @@
1
1
  export { wireGateway, createListenerMessageHandler, resolveGatewayAdapter, } from './gateway-runner.js';
2
- export type { GatewayAdapter, GatewayAdapterFactory, GatewayAttachment, GatewayInboundMessage, GatewayOutboundMessage, GatewayMeta, GatewaysMeta, GatewayTransportType, CoreGateway, PikkuGateway, WebhookVerificationResult, } from './gateway.types.js';
2
+ export type { GatewayAdapter, GatewayAdapterFactory, GatewayInboundMessage, GatewayOutboundMessage, GatewaysMeta, GatewayTransportType, CoreGateway, WebhookVerificationResult, } from './gateway.types.js';
@@ -173,6 +173,7 @@ const executeRoute = async (services, matchedRoute, http, options) => {
173
173
  getSession: () => userSession.get(),
174
174
  hasSessionChanged: () => userSession.sessionChanged,
175
175
  };
176
+ const statusBeforeRoute = http?.response?.statusCode;
176
177
  try {
177
178
  result = await runPikkuFunc('http', `${meta.method}:${meta.route}`, meta.pikkuFuncId, {
178
179
  singletonServices,
@@ -209,17 +210,22 @@ const executeRoute = async (services, matchedRoute, http, options) => {
209
210
  http?.response?.flushHeaders?.();
210
211
  }
211
212
  else {
213
+ const statusSetByRoute = http?.response?.statusCode !== statusBeforeRoute;
212
214
  if (result instanceof Response) {
213
215
  await applyWebResponse(http.response, result);
214
216
  }
215
217
  else if (result === undefined || result === null) {
216
- http?.response?.status(204);
218
+ if (!statusSetByRoute) {
219
+ http?.response?.status(204);
220
+ }
217
221
  }
218
222
  else if (route.returnsJSON === false) {
219
223
  http?.response?.arrayBuffer(result);
220
224
  }
221
225
  else {
222
- http?.response?.status(200);
226
+ if (!statusSetByRoute) {
227
+ http?.response?.status(200);
228
+ }
223
229
  http?.response?.json(result);
224
230
  }
225
231
  }
@@ -1,8 +1,7 @@
1
1
  export { PikkuFetchHTTPRequest, DEFAULT_MAX_BODY_SIZE, } from './pikku-fetch-http-request.js';
2
- export type { PikkuFetchHTTPRequestOptions } from './pikku-fetch-http-request.js';
3
2
  export { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js';
4
3
  export { logRoutes } from './log-http-routes.js';
5
4
  export { fetch, fetchData, wireHTTP, addHTTPMiddleware } from './http-runner.js';
6
5
  export { wireHTTPRoutes, defineHTTPRoutes } from './http-routes.js';
7
6
  export { toWebRequest, applyWebResponse } from './web-request.js';
8
- export type { AssertHTTPWiringParams, CoreHTTPFunctionWiring, HTTPMethod, HTTPRouteBaseConfig, HTTPRouteContract, HTTPRouteMap, HTTPWiringsMeta, PikkuHTTPRequest, PikkuHTTPResponse, PikkuQuery, RunHTTPWiringOptions, } from './http.types.js';
7
+ export type { AssertHTTPWiringParams, CoreHTTPFunctionWiring, HTTPMethod, HTTPRouteBaseConfig, HTTPRouteContract, HTTPRouteMap, HTTPWiringsMeta, PikkuHTTP, PikkuHTTPRequest, PikkuHTTPResponse, PikkuQuery, RunHTTPWiringOptions, } from './http.types.js';
@@ -1,4 +1,4 @@
1
1
  export { MCPEndpointRegistry } from './mcp-endpoint-registry.js';
2
2
  export { MCPError, wireMCPResource, wireMCPPrompt, runMCPResource, runMCPTool, runMCPPrompt, } from './mcp-runner.js';
3
3
  export { getMCPResourcesMeta, getMCPToolsMeta, getMCPPromptsMeta, } from './mcp-runner.js';
4
- export type { AssertMCPResourceURIParams, CoreMCPPrompt, CoreMCPResource, CoreMCPTool, MCPPromptResponse, MCPResourceMeta, MCPResourceResponse, MCPToolMeta, MCPToolResponse, MCPPromptMeta, PikkuMCP, } from './mcp.types.js';
4
+ export type { AssertMCPResourceURIParams, CoreMCPPrompt, CoreMCPResource, MCPPromptResponse, MCPResourceMeta, MCPResourceResponse, MCPToolMeta, MCPToolResponse, MCPPromptMeta, PikkuMCP, } from './mcp.types.js';
@@ -1,3 +1,4 @@
1
+ import type { PikkuHTTP } from '../http/http.types.js';
1
2
  import type { CoreMCPResource, CoreMCPPrompt, JsonRpcRequest, JsonRpcResponse, JsonRpcErrorResponse, PikkuMCP } from './mcp.types.js';
2
3
  import type { CorePikkuFunctionConfig, CorePikkuFunctionSessionless } from '../../function/functions.types.js';
3
4
  export declare class MCPError extends Error {
@@ -6,6 +7,20 @@ export declare class MCPError extends Error {
6
7
  }
7
8
  export type RunMCPEndpointParams<Tools extends string = any> = {
8
9
  mcp?: PikkuMCP<Tools>;
10
+ /**
11
+ * The HTTP request the MCP call arrived on, when it arrived over HTTP.
12
+ *
13
+ * Auth middleware reads the session off `wire.http.request` — every
14
+ * implementation opens with `if (!http?.request) return` — so without this an
15
+ * MCP call reaches the function unauthenticated no matter what middleware the
16
+ * app has registered, and a tool fronting a session-requiring function can
17
+ * only ever answer 'Authentication required'.
18
+ *
19
+ * Left undefined for transports that have no request to offer, such as stdio.
20
+ * Those remain anonymous, which is a property of the transport rather than a
21
+ * default chosen here.
22
+ */
23
+ http?: PikkuHTTP;
9
24
  /** Defaults to enabled outside production. */
10
25
  exposeErrors?: boolean;
11
26
  };
@@ -48,6 +48,11 @@ export async function runMCPResource(request, params, uri) {
48
48
  let extractedParams = {};
49
49
  const metas = pikkuState(null, 'mcp', 'resourcesMeta');
50
50
  const endpoints = pikkuState(null, 'mcp', 'resources');
51
+ // The key the resource's meta (and its middleware) is stored under. For a
52
+ // templated resource this is the template, not the concrete request URI — so
53
+ // it must be carried forward, or the meta lookup downstream misses and the
54
+ // resource's declared middleware (including auth) is silently skipped.
55
+ let metaKey = uri;
51
56
  if (endpoints.has(uri)) {
52
57
  endpoint = endpoints.get(uri);
53
58
  pikkuFuncId = metas[uri]?.pikkuFuncId;
@@ -60,6 +65,7 @@ export async function runMCPResource(request, params, uri) {
60
65
  const match = uri.match(regex);
61
66
  if (match) {
62
67
  endpoint = value;
68
+ metaKey = uriTemplate;
63
69
  pikkuFuncId = metas[uriTemplate]?.pikkuFuncId;
64
70
  for (let i = 0; i < paramNames.length; i++) {
65
71
  extractedParams[paramNames[i]] = match[i + 1]; // match[0] is the full match
@@ -71,7 +77,7 @@ export async function runMCPResource(request, params, uri) {
71
77
  return await runMCPPikkuFunc({
72
78
  ...request,
73
79
  params: { ...request.params, ...extractedParams },
74
- }, 'resource', uri, endpoint, pikkuFuncId, { ...params, mcp: { ...params.mcp, uri } });
80
+ }, 'resource', uri, endpoint, pikkuFuncId, { ...params, mcp: { ...params.mcp, uri } }, metaKey);
75
81
  }
76
82
  export async function runMCPTool(request, params, name) {
77
83
  const meta = pikkuState(null, 'mcp', 'toolsMeta')[name];
@@ -82,7 +88,11 @@ export async function runMCPPrompt(request, params, name) {
82
88
  const meta = pikkuState(null, 'mcp', 'promptsMeta')[name];
83
89
  return await runMCPPikkuFunc(request, 'prompt', name, endpoint, meta?.pikkuFuncId, params);
84
90
  }
85
- async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncId, { mcp: mcpWire, exposeErrors = !isProduction() }) {
91
+ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncId, { mcp: mcpWire, http, exposeErrors = !isProduction() },
92
+ // The key the endpoint's meta is stored under. Differs from `name` only for a
93
+ // templated resource, where `name` is the concrete URI and the meta lives
94
+ // under the template. Defaults to `name` for tools and prompts.
95
+ metaKey = name) {
86
96
  const singletonServices = getSingletonServices();
87
97
  const createWireServices = getCreateWireServices();
88
98
  try {
@@ -99,17 +109,20 @@ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncId, { mcp: mcp
99
109
  const mcpSessionService = new PikkuSessionService(singletonServices.sessionStore);
100
110
  const wire = {
101
111
  mcp: mcpWire,
112
+ // Only when the transport supplied one. Setting `http: undefined`
113
+ // explicitly would be the same to a reader and different to a spread.
114
+ ...(http ? { http } : {}),
102
115
  ...createMiddlewareSessionWireProps(mcpSessionService),
103
116
  };
104
117
  let meta;
105
118
  if (type === 'resource') {
106
- meta = pikkuState(null, 'mcp', 'resourcesMeta')[name];
119
+ meta = pikkuState(null, 'mcp', 'resourcesMeta')[metaKey];
107
120
  }
108
121
  else if (type === 'tool') {
109
- meta = pikkuState(null, 'mcp', 'toolsMeta')[name];
122
+ meta = pikkuState(null, 'mcp', 'toolsMeta')[metaKey];
110
123
  }
111
124
  else if (type === 'prompt') {
112
- meta = pikkuState(null, 'mcp', 'promptsMeta')[name];
125
+ meta = pikkuState(null, 'mcp', 'promptsMeta')[metaKey];
113
126
  }
114
127
  let resolvedFuncName = pikkuFuncId;
115
128
  let resolvedPackageName = meta?.packageName ?? null;
@@ -1,12 +1,11 @@
1
1
  export { definePersonas } from './define-personas.js';
2
2
  export { isRunnablePersona, roleMismatchMessage, runnablePersonas, validateAndBuildPersonasMeta, verifyPersonaRoles, } from './validate-personas.js';
3
3
  export type { RoleVerification } from './validate-personas.js';
4
- export { personaEnvironmentErrors, personaEnvironmentRefusal, resolvePersonaEnvironments, } from './persona-environments.js';
4
+ export { personaEnvironmentErrors, personaEnvironmentRefusal, } from './persona-environments.js';
5
5
  export type { PersonaEnvironment, PersonaEnvironmentSubject, } from './persona-environments.js';
6
- export { personaEmail, personaEmailLabel, personaEmails, } from './persona-email.js';
7
- export { allowedLinks, applyMailboxAllowlist, isAllowedSender, } from './persona-mailbox.js';
6
+ export { personaEmail, personaEmails, } from './persona-email.js';
8
7
  export type { MailboxAllowlist, PersonaMailbox, ReceivedEmail, } from './persona-mailbox.js';
9
- export type { CorePersona, CorePersonaAccount, CorePersonas, PersonaAccountMeta, PersonaDefinitions, PersonaMeta, PersonasMeta, } from './persona.types.js';
8
+ export type { CorePersona, CorePersonas, PersonaAccountMeta, PersonaDefinitions, PersonaMeta, PersonasMeta, } from './persona.types.js';
10
9
  /**
11
10
  * Signing in as a persona and talking to a stage over HTTP.
12
11
  *
@@ -1,8 +1,7 @@
1
1
  export { definePersonas } from './define-personas.js';
2
2
  export { isRunnablePersona, roleMismatchMessage, runnablePersonas, validateAndBuildPersonasMeta, verifyPersonaRoles, } from './validate-personas.js';
3
- export { personaEnvironmentErrors, personaEnvironmentRefusal, resolvePersonaEnvironments, } from './persona-environments.js';
4
- export { personaEmail, personaEmailLabel, personaEmails, } from './persona-email.js';
5
- export { allowedLinks, applyMailboxAllowlist, isAllowedSender, } from './persona-mailbox.js';
3
+ export { personaEnvironmentErrors, personaEnvironmentRefusal, } from './persona-environments.js';
4
+ export { personaEmail, personaEmails, } from './persona-email.js';
6
5
  /**
7
6
  * Signing in as a persona and talking to a stage over HTTP.
8
7
  *
@@ -1,9 +1,7 @@
1
1
  export type { ConfigValidationResult, CoreQueueWorker, GroupConcurrencyConfig, JobGroup, JobOptions, PikkuJobConfig, PikkuWorkerConfig, PikkuQueue, QueueJob, QueueJobStatus, QueueService, QueueWorkers, QueueWorkersMeta, } from './queue.types.js';
2
- export { wireQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
3
- export { QUEUE_IDENTITY_CLAIM_VERSION, QUEUE_IDENTITY_INFO, QUEUE_IDENTITY_SECRET_NAME, getQueueIdentitySecret, resolveQueueJobIdentity, signQueueIdentity, verifyQueueIdentity, } from './queue-identity.js';
2
+ export { wireQueueWorker, runQueueJob, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
4
3
  export type { QueueIdentityBinding } from './queue-identity.js';
5
4
  export { SignedQueueService } from './signed-queue-service.js';
6
- export { validateWorkerConfig } from './validate-worker-config.js';
7
5
  export type { QueueConfigMapping } from './validate-worker-config.js';
8
6
  export { registerQueueWorkers } from './register-queue-helper.js';
9
7
  export type { QueueRegistrationCallback } from './register-queue-helper.js';
@@ -1,5 +1,3 @@
1
- export { wireQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
2
- export { QUEUE_IDENTITY_CLAIM_VERSION, QUEUE_IDENTITY_INFO, QUEUE_IDENTITY_SECRET_NAME, getQueueIdentitySecret, resolveQueueJobIdentity, signQueueIdentity, verifyQueueIdentity, } from './queue-identity.js';
1
+ export { wireQueueWorker, runQueueJob, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
3
2
  export { SignedQueueService } from './signed-queue-service.js';
4
- export { validateWorkerConfig } from './validate-worker-config.js';
5
3
  export { registerQueueWorkers } from './register-queue-helper.js';
@@ -4,6 +4,10 @@ export type AddonInstance = {
4
4
  secretOverrides?: Record<string, string>;
5
5
  variableOverrides?: Record<string, string>;
6
6
  credentialOverrides?: Record<string, string>;
7
+ /** Set by the consuming app to opt this instance out of secret scoping. */
8
+ globalSecrets?: string;
9
+ /** Set by the consuming app to opt this instance out of credential scoping. */
10
+ globalCredentials?: string;
7
11
  };
8
12
  export declare const getOrCreatePackageSingletonServices: (packageName: string, parentServices: CoreSingletonServices, addonInstance?: AddonInstance) => Promise<CoreSingletonServices>;
9
13
  export declare const addonInstanceForNamespace: (namespace: string | undefined, expectedPackage: string) => AddonInstance | undefined;
@@ -1,4 +1,6 @@
1
1
  import { pikkuState } from '../../pikku-state.js';
2
+ import { ScopedSecretService } from '../../services/scoped-secret-service.js';
3
+ import { ScopedCredentialService } from '../../services/scoped-credential-service.js';
2
4
  const aliasSecretService = (secrets, overrides) => {
3
5
  const map = (key) => overrides[key] ?? key;
4
6
  return {
@@ -79,9 +81,6 @@ export const getOrCreatePackageSingletonServices = async (packageName, parentSer
79
81
  return cachedServices;
80
82
  }
81
83
  const factories = pikkuState(packageName, 'package', 'factories');
82
- if (!factories || !factories.createSingletonServices) {
83
- return parentServices;
84
- }
85
84
  let existingServices = parentServices;
86
85
  if (addonInstance?.secretOverrides && parentServices.secrets) {
87
86
  existingServices = {
@@ -95,6 +94,21 @@ export const getOrCreatePackageSingletonServices = async (packageName, parentSer
95
94
  variables: aliasVariablesService(parentServices.variables, addonInstance.variableOverrides),
96
95
  };
97
96
  }
97
+ if (!addonInstance?.globalSecrets && existingServices.secrets) {
98
+ existingServices = {
99
+ ...existingServices,
100
+ secrets: new ScopedSecretService(existingServices.secrets, new Set(pikkuState(packageName, 'package', 'declaredSecrets') ?? [])),
101
+ };
102
+ }
103
+ if (!addonInstance?.globalCredentials && existingServices.credentialService) {
104
+ existingServices = {
105
+ ...existingServices,
106
+ credentialService: new ScopedCredentialService(existingServices.credentialService, new Set(Object.keys(pikkuState(packageName, 'package', 'credentialsMeta') ?? {}))),
107
+ };
108
+ }
109
+ if (!factories || !factories.createSingletonServices) {
110
+ return existingServices;
111
+ }
98
112
  let config = existingServices.config;
99
113
  if (factories.createConfig) {
100
114
  config = await factories.createConfig(existingServices.variables);
@@ -118,5 +132,7 @@ export const addonInstanceForNamespace = (namespace, expectedPackage) => {
118
132
  secretOverrides: cfg.secretOverrides,
119
133
  variableOverrides: cfg.variableOverrides,
120
134
  credentialOverrides: cfg.credentialOverrides,
135
+ globalSecrets: cfg.globalSecrets,
136
+ globalCredentials: cfg.globalCredentials,
121
137
  };
122
138
  };
@@ -203,6 +203,8 @@ export class ContextAwareRPCService {
203
203
  secretOverrides: resolved.addonConfig?.secretOverrides,
204
204
  variableOverrides: resolved.addonConfig?.variableOverrides,
205
205
  credentialOverrides: resolved.addonConfig?.credentialOverrides,
206
+ globalSecrets: resolved.addonConfig?.globalSecrets,
207
+ globalCredentials: resolved.addonConfig?.globalCredentials,
206
208
  },
207
209
  };
208
210
  }
@@ -48,6 +48,10 @@ export interface ResolvedFunction {
48
48
  secretOverrides?: Record<string, string>;
49
49
  variableOverrides?: Record<string, string>;
50
50
  credentialOverrides?: Record<string, string>;
51
+ /** Set by the consuming app: hand this instance the unscoped `SecretService` */
52
+ globalSecrets?: string;
53
+ /** Set by the consuming app: hand this instance the unscoped `CredentialService` */
54
+ globalCredentials?: string;
51
55
  /** Set by `wireRemoteAddon`: dispatch this namespace's RPCs over HTTP */
52
56
  remote?: boolean;
53
57
  serverUrl?: string | ((services: any) => string | Promise<string>);
@@ -11,6 +11,19 @@ export type WireAddonConfig = {
11
11
  secretOverrides?: Record<string, string>;
12
12
  variableOverrides?: Record<string, string>;
13
13
  credentialOverrides?: Record<string, string>;
14
+ /**
15
+ * Hands this instance the whole `SecretService` instead of one scoped to the
16
+ * secrets it declared. The value is the reason, recorded in the deploy
17
+ * manifest — an addon that names secrets at runtime cannot be scoped, and
18
+ * only the consuming app, never the addon, can grant it.
19
+ */
20
+ globalSecrets?: string;
21
+ /**
22
+ * Hands this instance the whole `CredentialService` instead of one narrowed
23
+ * to the credentials it declared. The value is the reason, recorded in the
24
+ * deploy manifest, and only the consuming app can grant it.
25
+ */
26
+ globalCredentials?: string;
14
27
  };
15
28
  export declare const wireAddon: (config: WireAddonConfig) => void;
16
29
  /**
@@ -16,6 +16,10 @@ export const wireAddon = (config) => {
16
16
  ...(config.credentialOverrides
17
17
  ? { credentialOverrides: config.credentialOverrides }
18
18
  : {}),
19
+ ...(config.globalSecrets ? { globalSecrets: config.globalSecrets } : {}),
20
+ ...(config.globalCredentials
21
+ ? { globalCredentials: config.globalCredentials }
22
+ : {}),
19
23
  });
20
24
  };
21
25
  /**
@@ -1,3 +1,3 @@
1
1
  export { runScheduledTask, wireScheduler, getScheduledTasks, } from './scheduler-runner.js';
2
2
  export { logSchedulers } from './log-schedulers.js';
3
- export type { CoreScheduledTask, ScheduledTasksMeta, PikkuScheduledTask, } from './scheduler.types.js';
3
+ export type { CoreScheduledTask, ScheduledTasksMeta, } from './scheduler.types.js';
@@ -1,3 +1,3 @@
1
1
  export { wireTrigger, wireTriggerSource } from './trigger-runner.js';
2
2
  export { PikkuTriggerService } from './pikku-trigger-service.js';
3
- export type { TriggerMeta, TriggerSourceMeta, CorePikkuTriggerFunction, CorePikkuTriggerFunctionConfig, CoreTrigger, PikkuTrigger, } from './trigger.types.js';
3
+ export type { TriggerMeta, TriggerSourceMeta, CorePikkuTriggerFunction, CorePikkuTriggerFunctionConfig, CoreTrigger, } from './trigger.types.js';
@@ -16,15 +16,14 @@
16
16
  * {@link VirtualUserTarget}, which in production is an `HttpPersona`
17
17
  * signed in as a real user against staging or production.
18
18
  */
19
- export type { ApiCatalogueEntry, IntentRecord, IntentSource, IntentStatus, StepRecord, VirtualUserAction, VirtualUserBudget, VirtualUserDisposition, VirtualUserFinding, VirtualUserFindingKind, VirtualUserRunResult, VirtualUserTally, VirtualUserTarget, } from './virtual-user.types.js';
19
+ export type { ApiCatalogueEntry, IntentSource, VirtualUserDisposition, VirtualUserFinding, VirtualUserRunResult, VirtualUserTarget, } from './virtual-user.types.js';
20
20
  export { PRODUCTION_DISPOSITION } from './virtual-user.types.js';
21
- export { runVirtualUser, rememberIds, type RunVirtualUserParams, type VirtualUserCallContext, } from './run-virtual-user.js';
21
+ export { runVirtualUser, type RunVirtualUserParams, } from './run-virtual-user.js';
22
22
  export { personaScopes, prepareVirtualUserRun, type VirtualUserPreparation, } from './prepare-virtual-user-run.js';
23
23
  export type { VirtualUserRunOutcome, VirtualUserRunRecord, VirtualUserRunStart, VirtualUserRunStore, } from './virtual-user-run-store.js';
24
24
  export { DISPOSITIONS, dispositionProfile, type DispositionProfile, type VirtualUserTuning, } from './virtual-user-dispositions.js';
25
- export { catalogueClassification, catalogueIndex, catalogueLookup, describeEntry, isReadOnly, reachableCatalogue, renderCatalogue, unreachableCatalogue, } from './virtual-user-catalogue.js';
26
- export { reachableAgents, type AgentReachability, type ReachableAgent, } from './virtual-user-agents.js';
27
- export { IntentStack, intentsForPersona, type IntentMove, type ScheduledTick, } from './virtual-user-intents.js';
28
- export { createRng, type VirtualUserRng } from './virtual-user-rng.js';
25
+ export { catalogueClassification, catalogueLookup, isReadOnly, reachableCatalogue, unreachableCatalogue, } from './virtual-user-catalogue.js';
26
+ export { type AgentReachability, type ReachableAgent, } from './virtual-user-agents.js';
27
+ export { IntentStack, intentsForPersona, } from './virtual-user-intents.js';
29
28
  export { deriveCatalogue, deriveIntents, type SchemaMap, } from './virtual-user-derive.js';
30
29
  export { personaVirtualUserTarget, type PersonaTargetOptions, } from './virtual-user-target.js';
@@ -1,10 +1,8 @@
1
1
  export { PRODUCTION_DISPOSITION } from './virtual-user.types.js';
2
- export { runVirtualUser, rememberIds, } from './run-virtual-user.js';
2
+ export { runVirtualUser, } from './run-virtual-user.js';
3
3
  export { personaScopes, prepareVirtualUserRun, } from './prepare-virtual-user-run.js';
4
4
  export { DISPOSITIONS, dispositionProfile, } from './virtual-user-dispositions.js';
5
- export { catalogueClassification, catalogueIndex, catalogueLookup, describeEntry, isReadOnly, reachableCatalogue, renderCatalogue, unreachableCatalogue, } from './virtual-user-catalogue.js';
6
- export { reachableAgents, } from './virtual-user-agents.js';
5
+ export { catalogueClassification, catalogueLookup, isReadOnly, reachableCatalogue, unreachableCatalogue, } from './virtual-user-catalogue.js';
7
6
  export { IntentStack, intentsForPersona, } from './virtual-user-intents.js';
8
- export { createRng } from './virtual-user-rng.js';
9
7
  export { deriveCatalogue, deriveIntents, } from './virtual-user-derive.js';
10
8
  export { personaVirtualUserTarget, } from './virtual-user-target.js';
@@ -53,6 +53,19 @@ export interface WorkflowExpectServiceOptions extends WorkflowStepOptions {
53
53
  /** Assert the exact number of matching calls. Default: at least one. */
54
54
  times?: number;
55
55
  }
56
+ /** Options for workflow.expectScore() */
57
+ export interface WorkflowExpectScoreOptions extends WorkflowStepOptions {
58
+ /** Fail below this score. Defaults to 0.5 — a scorer that answered at all. */
59
+ atLeast?: number;
60
+ /** Fail above this score, for a scorer where high is the failure. */
61
+ atMost?: number;
62
+ /**
63
+ * The known-correct answer, which is what a reference-based judge grades
64
+ * against. Live traffic has no answer key, so a judge declared
65
+ * `requiresReference` is only ever reachable from a scenario.
66
+ */
67
+ reference?: string;
68
+ }
56
69
  /**
57
70
  * Type signature for workflow.do() RPC form - used by inspector
58
71
  */
@@ -80,10 +93,50 @@ export type WorkflowWireSleep = (stepName: string, duration: string) => Promise<
80
93
  * loops, like dynamic `do()` step names.
81
94
  */
82
95
  export type WorkflowWireSuspend = (reason: string) => Promise<void>;
96
+ /**
97
+ * Who is allowed to answer an approval gate, relative to the user who started
98
+ * the run.
99
+ *
100
+ * - `any` — anyone the approve entrypoint lets through. The gate is a pause for
101
+ * a decision, not an authorization boundary.
102
+ * - `owner` — only the user who started the run, so a gate can be used to
103
+ * confirm an action against the same person who requested it.
104
+ * - `not-initiator` — anyone *except* the user who started the run: four-eyes.
105
+ * A run with no recorded initiator has nobody to exclude, so this degrades to
106
+ * requiring a signed-in decider.
107
+ */
108
+ export type WorkflowApprovalApprovers = 'any' | 'owner' | 'not-initiator';
109
+ /**
110
+ * Who may answer an approval gate. Declared on the gate rather than the
111
+ * entrypoint because it is a property of the decision being made, and because
112
+ * one workflow can hold several gates with different requirements.
113
+ */
114
+ export interface WorkflowApprovalPolicy {
115
+ /**
116
+ * Defaults to `any`. See {@link WorkflowApprovalApprovers}.
117
+ */
118
+ approvers?: WorkflowApprovalApprovers;
119
+ /**
120
+ * Additionally require the decider's session to hold this scope, so
121
+ * "a second pair of eyes" can be narrowed to "a second pair of *senior*
122
+ * eyes". Combines with `approvers` — both must pass.
123
+ */
124
+ approverScope?: string;
125
+ }
126
+ /**
127
+ * The decider, reduced to the two facts a policy can be expressed in terms of.
128
+ * Recorded alongside the decision so the gate can be judged on replay — the
129
+ * session itself is long gone by then — and carried into the settled outcome so
130
+ * the answer keeps its provenance.
131
+ */
132
+ export interface ApprovalDecider {
133
+ userId?: string;
134
+ scopes?: string[];
135
+ }
83
136
  /**
84
137
  * Options for workflow.approval().
85
138
  */
86
- export interface WorkflowApprovalOptions<TSchema extends StandardSchemaV1 = StandardSchemaV1> {
139
+ export interface WorkflowApprovalOptions<TSchema extends StandardSchemaV1 = StandardSchemaV1> extends WorkflowApprovalPolicy {
87
140
  /**
88
141
  * Schema the decision payload is validated against. This is a VALUE, not a
89
142
  * type generic: the payload arrives from an untrusted caller over the approve
@@ -107,6 +160,14 @@ export interface WorkflowApprovalOptions<TSchema extends StandardSchemaV1 = Stan
107
160
  export type ApprovalOutcome<T> = {
108
161
  status: 'decided';
109
162
  data: T;
163
+ /**
164
+ * Who answered, and when. Run state holds the decision only while the gate
165
+ * is open — it is overwritten by the next write and cleared outright when a
166
+ * decision is refused — so the settled answer carries its own provenance
167
+ * into the step result, which is append-only.
168
+ */
169
+ decidedBy?: ApprovalDecider;
170
+ decidedAt?: string;
110
171
  } | {
111
172
  status: 'expired';
112
173
  };
@@ -386,6 +447,10 @@ export interface ApprovalStepMeta {
386
447
  outputVar?: string;
387
448
  /** Expiry duration, when one was given */
388
449
  expiry?: string | number;
450
+ /** Who may answer the gate, when the default was not used */
451
+ approvers?: WorkflowApprovalApprovers;
452
+ /** Scope the decider must hold, when one was required */
453
+ approverScope?: string;
389
454
  }
390
455
  /**
391
456
  * Filter step metadata (array.filter)
@@ -476,25 +541,21 @@ export interface PikkuWorkflowWire {
476
541
  /**
477
542
  * What a scenario has accumulated so far, shared by its body and its hooks.
478
543
  *
479
- * Typed as a partial of the scenario's own output because a scenario can fail
480
- * at any step: the shape describes what a *completed* run produces, and the
481
- * context holds however much of it this run reached. A scenario declaring no
482
- * object output gets an open record rather than `Partial<never>`.
544
+ * `Partial` because a scenario can fail at any step: `Out` describes what a
545
+ * *completed* run produces, and the context holds however much of it this run
546
+ * reached.
483
547
  *
484
- * `any` collapses to `any`, not to the union of both branches. `PikkuWire`'s
485
- * default depends on that: the wire type is also used as a generic *constraint*,
486
- * and a constraint carrying a concrete context would reject every wire whose
487
- * scenario output differs from it.
548
+ * The `0 extends 1 & Out` branch keeps `any` collapsing to `any` rather than to
549
+ * the union of the other two. `PikkuWire` and `PikkuFunctionConfig` both use
550
+ * this type as a generic *constraint*, and a constraint carrying a concrete
551
+ * context would reject every wire whose scenario output differs from it.
488
552
  */
489
553
  export type ScenarioContext<Out = unknown> = 0 extends 1 & Out ? any : Out extends object ? Partial<Out> : Record<string, unknown>;
490
554
  export interface PikkuScenarioWire<Out = unknown> extends PikkuWorkflowWire {
491
555
  /**
492
- * Scratch the body writes and the `before`/`after` hooks read.
493
- *
494
- * A hook is a separate function, so it cannot see the body's locals — which
495
- * is why teardown had nowhere to learn the ids the body minted. Assigning
496
- * them here (`scenario.context.projectId = project.projectId`) hands them to
497
- * `after`, which runs in a `finally` and so cleans up on a failed run too.
556
+ * Scratch the body writes and the `before`/`after` hooks read. A hook is a
557
+ * separate function and cannot see the body's locals, so this is how teardown
558
+ * learns the ids the body minted.
498
559
  *
499
560
  * Deliberately *not* a world: it is scoped to one run, and scenario steps
500
561
  * cannot reach it. Steps stay pure functions of their declared inputs, so the
@@ -510,6 +571,15 @@ export interface PikkuScenarioWire<Out = unknown> extends PikkuWorkflowWire {
510
571
  expectError: <TInput = any>(stepName: string, rpcName: string, data: TInput, options?: WorkflowExpectErrorOptions) => Promise<string>;
511
572
  /** Stub-assertion step: asserts `service.method` was called on the target server */
512
573
  expectService: (stepName: string, serviceMethod: string, options?: WorkflowExpectServiceOptions) => Promise<void>;
574
+ /**
575
+ * Grade-assertion step: runs one declared scorer against a finished agent run
576
+ * and asserts the score. Returns the grade, so a scenario can report the
577
+ * reason a judge gave rather than only that it fell short.
578
+ */
579
+ expectScore: (stepName: string, runId: string, scorerName: string, options?: WorkflowExpectScoreOptions) => Promise<{
580
+ score: number;
581
+ reason?: string;
582
+ }>;
513
583
  /**
514
584
  * Run a registered scenario step, as the setup the scenario starts from.
515
585
  *
@@ -2,23 +2,23 @@ export { PikkuWorkflowService } from './pikku-workflow-service.js';
2
2
  export { WorkflowCancelledException, WorkflowSuspendedException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowApprovalResolvedError, WorkflowStepFunctionMismatchError, } from './workflow-errors.js';
3
3
  export { DEFAULT_STEP_RETRIES } from './workflow-constants.js';
4
4
  export { assertWorkflowRunOwner, WorkflowRunForbiddenError, } from './workflow-run-ownership.js';
5
- export type { RunLifecycleContext, WorkflowRunEngine, WorkflowRunExtension, } from './workflow-run-engine.types.js';
5
+ export { WorkflowApprovalForbiddenError } from './workflow-approval-policy.js';
6
+ export type { WorkflowRunEngine, WorkflowRunExtension, } from './workflow-run-engine.types.js';
6
7
  export { deriveInvocationId, uuidv5 } from './workflow-invocation-id.js';
7
8
  export { isRef } from './graph/workflow-graph.types.js';
8
9
  export type { RefValue } from './graph/workflow-graph.types.js';
9
10
  export { buildRunTimeline, reconstructStateAt, reconstructFinalState, } from './run-timeline.js';
10
- export type { RunTimeline, RunTimelineEvent, ReconstructedRunState, ReconstructedStep, RunPhase, } from './run-timeline.js';
11
+ export type { RunTimeline, ReconstructedRunState, } from './run-timeline.js';
11
12
  export { addWorkflow } from './dsl/workflow-runner.js';
12
13
  export { addFeature, resolveFeatureScenarios } from './feature.js';
13
14
  export { template, type TemplateString } from './graph/template.js';
14
15
  export { pikkuWorkflowGraph, type PikkuWorkflowGraphConfig, type PikkuWorkflowGraphResult, } from './graph/wire-workflow-graph.js';
15
- export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
16
16
  export type { WorkflowStepInput as WorkflowStepQueueInput, PikkuWorkflowOrchestratorInput, PikkuWorkflowSleeperInput, } from './workflow-queue-workers.js';
17
- export type { WorkflowService, WorkflowQueueOptions, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, CoreFeature, CoreFeatureScenario, FeatureMeta, FeatureMetaEntry, FeaturesMeta, FeaturePlanEntry, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, } from './workflow.types.js';
18
- export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowWireDoInline, WorkflowWireSleep, WorkflowWireSuspend, WorkflowWireApproval, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, SimpleCondition, Condition, BranchCase, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, ScenarioStepInvocation, ScenarioStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, PikkuScenarioWire, } from './workflow.types.js';
17
+ export type { WorkflowService, WorkflowQueueOptions, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, CoreFeature, FeatureMeta, FeaturesMeta, FeaturePlanEntry, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowsRuntimeMeta, } from './workflow.types.js';
18
+ export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, Condition, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, ScenarioStepInvocation, ScenarioStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, PikkuScenarioWire, } from './workflow.types.js';
19
19
  export type { ScenarioStepPhase, ScenarioStepKind, ScenarioStepOptions, PikkuScenarioStepWire, ScenarioEnvironment, ScenarioSurface, ScenarioSurfaceResolution, } from './scenario-step.types.js';
20
20
  export { SCENARIO_SURFACES } from './scenario-step.types.js';
21
- export { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js';
21
+ export { resolveScenarioSurfaces } from './scenario-surface.js';
22
22
  export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
23
23
  export { pollUntil, type PollOptions } from './scenario-poll.js';
24
24
  export { createCookieJar } from './scenario-cookie-jar.js';
@@ -2,6 +2,7 @@ export { PikkuWorkflowService } from './pikku-workflow-service.js';
2
2
  export { WorkflowCancelledException, WorkflowSuspendedException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowApprovalResolvedError, WorkflowStepFunctionMismatchError, } from './workflow-errors.js';
3
3
  export { DEFAULT_STEP_RETRIES } from './workflow-constants.js';
4
4
  export { assertWorkflowRunOwner, WorkflowRunForbiddenError, } from './workflow-run-ownership.js';
5
+ export { WorkflowApprovalForbiddenError } from './workflow-approval-policy.js';
5
6
  export { deriveInvocationId, uuidv5 } from './workflow-invocation-id.js';
6
7
  export { isRef } from './graph/workflow-graph.types.js';
7
8
  export { buildRunTimeline, reconstructStateAt, reconstructFinalState, } from './run-timeline.js';
@@ -9,10 +10,9 @@ export { addWorkflow } from './dsl/workflow-runner.js';
9
10
  export { addFeature, resolveFeatureScenarios } from './feature.js';
10
11
  export { template } from './graph/template.js';
11
12
  export { pikkuWorkflowGraph, } from './graph/wire-workflow-graph.js';
12
- export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
13
13
  export { SCENARIO_SURFACES } from './scenario-step.types.js';
14
14
  // Which of a step's bindings run: one for an action, every witness for a `then`
15
- export { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js';
15
+ export { resolveScenarioSurfaces } from './scenario-surface.js';
16
16
  export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
17
17
  export { pollUntil } from './scenario-poll.js';
18
18
  export { createCookieJar } from './scenario-cookie-jar.js';