@pikku/core 0.12.63 → 0.12.64

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 (198) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/dev/hot-reload.js +13 -55
  3. package/dist/dev/module-runner.d.ts +14 -0
  4. package/dist/dev/module-runner.js +59 -0
  5. package/dist/dev/reload-meta.d.ts +13 -0
  6. package/dist/dev/reload-meta.js +22 -0
  7. package/dist/errors/errors.d.ts +12 -0
  8. package/dist/errors/errors.js +19 -0
  9. package/dist/function/function-runner.d.ts +5 -5
  10. package/dist/function/function-runner.js +39 -111
  11. package/dist/function/functions.types.d.ts +17 -7
  12. package/dist/index.d.ts +5 -1
  13. package/dist/index.js +3 -1
  14. package/dist/middleware/auth-apikey.d.ts +1 -0
  15. package/dist/middleware/auth-bearer.d.ts +1 -0
  16. package/dist/middleware/auth-cookie.d.ts +1 -0
  17. package/dist/middleware/cors.d.ts +1 -0
  18. package/dist/middleware/index.d.ts +1 -1
  19. package/dist/middleware/index.js +1 -1
  20. package/dist/middleware/remote-auth.d.ts +1 -0
  21. package/dist/middleware/telemetry.d.ts +2 -0
  22. package/dist/middleware/timeout.d.ts +1 -0
  23. package/dist/permissions.d.ts +32 -25
  24. package/dist/permissions.js +104 -185
  25. package/dist/pikku-state.js +0 -2
  26. package/dist/scopes.d.ts +17 -0
  27. package/dist/scopes.js +59 -0
  28. package/dist/services/ai-embedding-service.d.ts +31 -0
  29. package/dist/services/ai-embedding-service.js +1 -0
  30. package/dist/services/credential-wire-service.d.ts +3 -1
  31. package/dist/services/credential-wire-service.js +10 -4
  32. package/dist/services/index.d.ts +4 -0
  33. package/dist/services/index.js +2 -0
  34. package/dist/services/meta-service.d.ts +15 -2
  35. package/dist/services/meta-service.js +51 -1
  36. package/dist/services/queue-webhook-service.d.ts +55 -0
  37. package/dist/services/queue-webhook-service.js +136 -0
  38. package/dist/services/scope-service.d.ts +67 -0
  39. package/dist/services/scope-service.js +1 -0
  40. package/dist/services/typed-secret-service.d.ts +8 -0
  41. package/dist/services/typed-secret-service.js +39 -4
  42. package/dist/services/webhook-service.d.ts +140 -0
  43. package/dist/services/webhook-service.js +44 -0
  44. package/dist/services/workflow-service.d.ts +7 -0
  45. package/dist/types/core.types.d.ts +34 -6
  46. package/dist/types/state.types.d.ts +20 -2
  47. package/dist/utils/hmac.d.ts +16 -0
  48. package/dist/utils/hmac.js +26 -0
  49. package/dist/utils/safe-fetch.d.ts +51 -0
  50. package/dist/utils/safe-fetch.js +192 -0
  51. package/dist/wirings/ai-agent/ai-agent-agui.d.ts +84 -0
  52. package/dist/wirings/ai-agent/ai-agent-agui.js +305 -0
  53. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +41 -1
  54. package/dist/wirings/ai-agent/ai-agent-prepare.js +146 -6
  55. package/dist/wirings/ai-agent/ai-agent-runner.js +6 -1
  56. package/dist/wirings/ai-agent/ai-agent-stream.js +61 -10
  57. package/dist/wirings/ai-agent/ai-agent.types.d.ts +27 -1
  58. package/dist/wirings/ai-agent/index.d.ts +1 -0
  59. package/dist/wirings/ai-agent/index.js +1 -0
  60. package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
  61. package/dist/wirings/ai-agent/voice-input.js +2 -38
  62. package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
  63. package/dist/wirings/channel/channel-common.js +0 -1
  64. package/dist/wirings/channel/channel-handler.js +1 -4
  65. package/dist/wirings/channel/channel-middleware-runner.d.ts +12 -0
  66. package/dist/wirings/channel/channel-middleware-runner.js +34 -19
  67. package/dist/wirings/channel/channel.types.d.ts +2 -6
  68. package/dist/wirings/cli/cli-runner.js +0 -2
  69. package/dist/wirings/cli/cli.types.d.ts +1 -2
  70. package/dist/wirings/http/http-routes.js +0 -3
  71. package/dist/wirings/http/http-runner.d.ts +9 -29
  72. package/dist/wirings/http/http-runner.js +9 -34
  73. package/dist/wirings/http/http.types.d.ts +1 -9
  74. package/dist/wirings/mcp/mcp-runner.js +0 -2
  75. package/dist/wirings/mcp/mcp.types.d.ts +5 -11
  76. package/dist/wirings/oauth2/index.d.ts +0 -3
  77. package/dist/wirings/oauth2/index.js +1 -2
  78. package/dist/wirings/rpc/addon-runner.d.ts +28 -0
  79. package/dist/wirings/rpc/addon-runner.js +173 -0
  80. package/dist/wirings/rpc/index.d.ts +5 -1
  81. package/dist/wirings/rpc/index.js +3 -1
  82. package/dist/wirings/rpc/remote-addon-auth.d.ts +26 -0
  83. package/dist/wirings/rpc/remote-addon-auth.js +43 -0
  84. package/dist/wirings/rpc/rpc-runner.d.ts +19 -0
  85. package/dist/wirings/rpc/rpc-runner.js +109 -3
  86. package/dist/wirings/rpc/rpc-types.d.ts +15 -1
  87. package/dist/wirings/rpc/wire-addon.js +9 -0
  88. package/dist/wirings/rpc/wire-remote-addon.d.ts +47 -0
  89. package/dist/wirings/rpc/wire-remote-addon.js +19 -0
  90. package/dist/wirings/scope/index.d.ts +3 -0
  91. package/dist/wirings/scope/index.js +2 -0
  92. package/dist/wirings/scope/scope.types.d.ts +39 -0
  93. package/dist/wirings/scope/scope.types.js +1 -0
  94. package/dist/wirings/scope/validate-scope-definitions.d.ts +16 -0
  95. package/dist/wirings/scope/validate-scope-definitions.js +76 -0
  96. package/dist/wirings/scope/wire-scope.d.ts +33 -0
  97. package/dist/wirings/scope/wire-scope.js +32 -0
  98. package/dist/wirings/workflow/dsl/index.d.ts +1 -1
  99. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +53 -1
  100. package/dist/wirings/workflow/graph/graph-node.d.ts +2 -0
  101. package/dist/wirings/workflow/graph/graph-runner.js +3 -0
  102. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +6 -0
  103. package/dist/wirings/workflow/graph/wire-workflow-graph.js +1 -0
  104. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +5 -0
  105. package/dist/wirings/workflow/index.d.ts +2 -2
  106. package/dist/wirings/workflow/index.js +1 -1
  107. package/dist/wirings/workflow/pikku-workflow-service.d.ts +51 -1
  108. package/dist/wirings/workflow/pikku-workflow-service.js +171 -0
  109. package/dist/wirings/workflow/workflow.types.d.ts +1 -1
  110. package/package.json +5 -1
  111. package/run-tests.sh +1 -0
  112. package/src/dev/hot-reload.ts +13 -68
  113. package/src/dev/module-runner.test.ts +169 -0
  114. package/src/dev/module-runner.ts +103 -0
  115. package/src/dev/reload-meta.test.ts +31 -2
  116. package/src/dev/reload-meta.ts +26 -0
  117. package/src/errors/errors.ts +24 -0
  118. package/src/function/function-runner.test.ts +276 -142
  119. package/src/function/function-runner.ts +52 -145
  120. package/src/function/functions.types.ts +28 -38
  121. package/src/index.ts +8 -6
  122. package/src/middleware/index.ts +1 -5
  123. package/src/permissions.test.ts +153 -372
  124. package/src/permissions.ts +139 -259
  125. package/src/pikku-state.ts +0 -2
  126. package/src/scopes.test.ts +167 -0
  127. package/src/scopes.ts +69 -0
  128. package/src/services/ai-embedding-service.ts +31 -0
  129. package/src/services/credential-wire-service.ts +10 -4
  130. package/src/services/index.ts +20 -0
  131. package/src/services/meta-service.ts +68 -3
  132. package/src/services/queue-webhook-service.test.ts +408 -0
  133. package/src/services/queue-webhook-service.ts +182 -0
  134. package/src/services/scope-service.ts +75 -0
  135. package/src/services/typed-secret-service.test.ts +35 -0
  136. package/src/services/typed-secret-service.ts +39 -4
  137. package/src/services/webhook-service.ts +180 -0
  138. package/src/services/workflow-service.ts +7 -0
  139. package/src/types/core.types.ts +42 -14
  140. package/src/types/state.types.ts +23 -3
  141. package/src/utils/hmac.ts +27 -0
  142. package/src/utils/safe-fetch.test.ts +373 -0
  143. package/src/utils/safe-fetch.ts +213 -0
  144. package/src/wirings/ai-agent/ai-agent-agui.test.ts +1127 -0
  145. package/src/wirings/ai-agent/ai-agent-agui.ts +386 -0
  146. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +308 -0
  147. package/src/wirings/ai-agent/ai-agent-prepare.ts +192 -8
  148. package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
  149. package/src/wirings/ai-agent/ai-agent-runner.ts +21 -0
  150. package/src/wirings/ai-agent/ai-agent-stream.test.ts +198 -10
  151. package/src/wirings/ai-agent/ai-agent-stream.ts +84 -10
  152. package/src/wirings/ai-agent/ai-agent.types.ts +28 -0
  153. package/src/wirings/ai-agent/index.ts +1 -0
  154. package/src/wirings/ai-agent/voice-input.ts +6 -35
  155. package/src/wirings/channel/channel-common.ts +0 -1
  156. package/src/wirings/channel/channel-handler.ts +1 -9
  157. package/src/wirings/channel/channel-middleware-runner.test.ts +28 -2
  158. package/src/wirings/channel/channel-middleware-runner.ts +39 -27
  159. package/src/wirings/channel/channel.types.ts +0 -6
  160. package/src/wirings/cli/cli-runner.ts +0 -2
  161. package/src/wirings/cli/cli.types.ts +0 -2
  162. package/src/wirings/http/http-routes.ts +0 -3
  163. package/src/wirings/http/http-runner.test.ts +1 -11
  164. package/src/wirings/http/http-runner.ts +16 -39
  165. package/src/wirings/http/http.types.ts +1 -10
  166. package/src/wirings/mcp/mcp-runner.ts +0 -2
  167. package/src/wirings/mcp/mcp.types.ts +3 -11
  168. package/src/wirings/oauth2/index.ts +0 -3
  169. package/src/wirings/rpc/addon-runner.ts +233 -0
  170. package/src/wirings/rpc/index.ts +17 -1
  171. package/src/wirings/rpc/remote-addon-auth.ts +69 -0
  172. package/src/wirings/rpc/rpc-runner.test.ts +319 -16
  173. package/src/wirings/rpc/rpc-runner.ts +155 -3
  174. package/src/wirings/rpc/rpc-types.ts +16 -5
  175. package/src/wirings/rpc/wire-addon.test.ts +6 -3
  176. package/src/wirings/rpc/wire-addon.ts +9 -0
  177. package/src/wirings/rpc/wire-remote-addon.ts +57 -0
  178. package/src/wirings/scope/index.ts +14 -0
  179. package/src/wirings/scope/scope.test.ts +135 -0
  180. package/src/wirings/scope/scope.types.ts +44 -0
  181. package/src/wirings/scope/validate-scope-definitions.ts +110 -0
  182. package/src/wirings/scope/wire-scope.ts +34 -0
  183. package/src/wirings/workflow/dsl/index.ts +4 -0
  184. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +61 -0
  185. package/src/wirings/workflow/graph/graph-node.ts +2 -0
  186. package/src/wirings/workflow/graph/graph-runner.test.ts +58 -0
  187. package/src/wirings/workflow/graph/graph-runner.ts +3 -0
  188. package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
  189. package/src/wirings/workflow/graph/workflow-graph.types.ts +5 -0
  190. package/src/wirings/workflow/index.ts +5 -0
  191. package/src/wirings/workflow/pikku-workflow-service.test.ts +341 -0
  192. package/src/wirings/workflow/pikku-workflow-service.ts +236 -0
  193. package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
  194. package/src/wirings/workflow/workflow.types.ts +4 -0
  195. package/tsconfig.tsbuildinfo +1 -1
  196. package/src/wirings/oauth2/oauth2-client.test.ts +0 -930
  197. package/src/wirings/oauth2/oauth2-client.ts +0 -331
  198. package/src/wirings/oauth2/oauth2-routes.ts +0 -234
@@ -6,10 +6,12 @@ import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
6
6
  import { parseVersionedId } from '../version.js';
7
7
  import { PikkuSessionService } from '../services/user-session-service.js';
8
8
  import { ForbiddenError, ReadonlySessionError } from '../errors/errors.js';
9
+ import { verifyScopes } from '../scopes.js';
9
10
  import { PikkuCredentialWireService, createWireServicesCredentialWireProps, } from '../services/credential-wire-service.js';
10
11
  import { defaultPikkuUserIdResolver } from '../services/pikku-user-id.js';
11
12
  import { createInvocationAudit, resolveAuditConfig, } from '../services/audit-service.js';
12
13
  import { rpcService } from '../wirings/rpc/rpc-runner.js';
14
+ import { getOrCreatePackageSingletonServices } from '../wirings/rpc/addon-runner.js';
13
15
  import { closeWireServices } from '../utils.js';
14
16
  async function resolveSession(wire, singletonServices, sessionService) {
15
17
  const pikkuUserId = defaultPikkuUserIdResolver(wire);
@@ -35,88 +37,6 @@ async function resolveSession(wire, singletonServices, sessionService) {
35
37
  sessionService?.setInitial(wire.session);
36
38
  }
37
39
  }
38
- /**
39
- * Get or create singleton services for an addon package.
40
- * Services are cached in pikkuState to avoid recreation on each call.
41
- *
42
- * @param packageName - The addon package name
43
- * @param parentServices - The parent/caller's singleton services (used as base)
44
- * @returns The package's singleton services
45
- */
46
- /**
47
- * Find the consumer-defined namespace (from wireAddon) for a given addon package.
48
- * Returns null if the package isn't registered as an addon.
49
- */
50
- const findAddonNamespaceForPackage = (packageName) => {
51
- const addons = pikkuState(null, 'addons', 'packages');
52
- if (!addons)
53
- return null;
54
- for (const [namespace, cfg] of addons.entries()) {
55
- if (cfg?.package === packageName)
56
- return namespace;
57
- }
58
- return null;
59
- };
60
- /**
61
- * Wrap a workflow service so that bare workflow names passed from inside an
62
- * addon function are auto-prefixed with the addon's consumer-facing namespace.
63
- * Without this, `runToCompletion('myWorkflow')` from inside an addon misses
64
- * the workflow registered under the addon's package scope and throws
65
- * WorkflowNotFoundError — forcing addons to hardcode their consumer-defined
66
- * namespace, which couples the addon to its caller.
67
- *
68
- * Explicit `'ns:name'` and bare names that already exist in root meta are
69
- * unaffected; only bare names that would otherwise miss resolution get
70
- * prefixed.
71
- */
72
- const wrapWorkflowServiceForPackage = (service, packageName) => {
73
- return new Proxy(service, {
74
- get(target, prop, receiver) {
75
- if (prop === 'startWorkflow' || prop === 'runToCompletion') {
76
- const original = Reflect.get(target, prop, receiver);
77
- return function (name, ...rest) {
78
- if (typeof name === 'string' && !name.includes(':')) {
79
- const namespace = findAddonNamespaceForPackage(packageName);
80
- if (namespace) {
81
- name = `${namespace}:${name}`;
82
- }
83
- }
84
- return original.call(this, name, ...rest);
85
- };
86
- }
87
- return Reflect.get(target, prop, receiver);
88
- },
89
- });
90
- };
91
- const getOrCreatePackageSingletonServices = async (packageName, parentServices) => {
92
- // Check if we already have cached singleton services for this package
93
- const cachedServices = pikkuState(packageName, 'package', 'singletonServices');
94
- if (cachedServices) {
95
- return cachedServices;
96
- }
97
- // Get the package's service factories
98
- const factories = pikkuState(packageName, 'package', 'factories');
99
- if (!factories || !factories.createSingletonServices) {
100
- // No factories registered, use parent services
101
- return parentServices;
102
- }
103
- // Create config for the package (use parent config if no factory)
104
- let config = parentServices.config;
105
- if (factories.createConfig) {
106
- config = await factories.createConfig(parentServices.variables);
107
- }
108
- // Create singleton services for the package, passing parent services as existing
109
- const packageServices = await factories.createSingletonServices(config, parentServices);
110
- // Wrap workflowService so that bare names used inside the addon's functions
111
- // resolve to workflows registered under the addon's package scope.
112
- if (packageServices.workflowService &&
113
- typeof packageServices.workflowService === 'object') {
114
- packageServices.workflowService = wrapWorkflowServiceForPackage(packageServices.workflowService, packageName);
115
- }
116
- // Cache the services
117
- pikkuState(packageName, 'package', 'singletonServices', packageServices);
118
- return packageServices;
119
- };
120
40
  export const addFunction = (funcName, funcConfig, packageName = null) => {
121
41
  pikkuState(packageName, 'function', 'functions').set(funcName, funcConfig);
122
42
  };
@@ -137,7 +57,7 @@ export const getAllFunctionNames = () => {
137
57
  }
138
58
  return functions;
139
59
  };
140
- export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, inheritedPermissions, wirePermissions, coerceDataFromSchema, tags = [], wire, sessionService, credentialWireService, packageName = null, }) => {
60
+ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, coerceDataFromSchema, wire, sessionService, credentialWireService, packageName = null, addonInstance, }) => {
141
61
  wire.wireType ??= wireType;
142
62
  wire.wireId ??= wireId;
143
63
  const funcMap = pikkuState(packageName, 'function', 'functions');
@@ -163,7 +83,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
163
83
  const resolvedFunctionId = funcMeta.pikkuFuncId ?? funcName;
164
84
  // For addon packages, get or create their singleton services
165
85
  const resolvedSingletonServices = packageName
166
- ? await getOrCreatePackageSingletonServices(packageName, singletonServices)
86
+ ? await getOrCreatePackageSingletonServices(packageName, singletonServices, addonInstance)
167
87
  : singletonServices;
168
88
  // Get the package's createWireServices if available
169
89
  let resolvedCreateWireServices = createWireServices;
@@ -182,8 +102,18 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
182
102
  ? wrapChannelWithMiddleware(wire, resolvedSingletonServices, allChannelMiddleware)
183
103
  : wire;
184
104
  // Set up credential wire service early so middleware can use setCredential.
185
- // Skip if already set up (e.g. addon functions reuse the parent wire).
186
- if (!resolvedWire.getCredentials) {
105
+ // An addon instance with credentialOverrides always gets a fresh alias-aware
106
+ // service so its logical credential names resolve to instance-specific ones,
107
+ // even when a parent credential service is already present on the wire.
108
+ // Otherwise skip if already set up (e.g. addon functions reuse the parent wire).
109
+ if (addonInstance?.credentialOverrides) {
110
+ // Credentials belong to the consuming project, so resolve them via the
111
+ // project's credentialService (the addon's own singletons may not carry it).
112
+ const aliasedCredentialWireService = new PikkuCredentialWireService(singletonServices.credentialService ??
113
+ resolvedSingletonServices.credentialService, resolvedWire, addonInstance.credentialOverrides);
114
+ Object.assign(resolvedWire, createWireServicesCredentialWireProps(aliasedCredentialWireService));
115
+ }
116
+ else if (!resolvedWire.getCredentials) {
187
117
  const resolvedCredentialWireService = credentialWireService ??
188
118
  new PikkuCredentialWireService(resolvedSingletonServices.credentialService, resolvedWire);
189
119
  Object.assign(resolvedWire, createWireServicesCredentialWireProps(resolvedCredentialWireService));
@@ -192,19 +122,14 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
192
122
  const invocationWire = resolvedWire;
193
123
  const previousFunctionId = invocationWire.functionId;
194
124
  const previousAudit = invocationWire.audit;
125
+ const previousAddonNamespace = invocationWire.addonNamespace;
195
126
  const previousRpcDescriptor = Object.getOwnPropertyDescriptor(invocationWire, 'rpc');
196
127
  invocationWire.functionId = resolvedFunctionId;
197
128
  invocationWire.audit = resolvedAuditConfig;
198
- // Convert tags to PermissionMetadata and merge with inheritedPermissions
199
- let mergedInheritedPermissions;
200
- if (tags && tags.length > 0) {
201
- mergedInheritedPermissions = [
202
- ...(inheritedPermissions || []),
203
- ...tags.map((tag) => ({ type: 'tag', tag })),
204
- ];
205
- }
206
- else {
207
- mergedInheritedPermissions = inheritedPermissions || [];
129
+ // Track which addon instance is executing so intra-addon sibling calls
130
+ // resolve the same per-instance singleton services and overrides.
131
+ if (addonInstance) {
132
+ invocationWire.addonNamespace = addonInstance.namespace;
208
133
  }
209
134
  // Helper function to run permissions and execute the function
210
135
  const executeFunction = async () => {
@@ -235,6 +160,11 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
235
160
  if (session?.readonly && !funcMeta.readonly) {
236
161
  throw new ReadonlySessionError();
237
162
  }
163
+ // Scopes gate before the data is evaluated: they depend only on the
164
+ // session, so a denied request never pays to parse or validate its body.
165
+ // Scopes are an AND gate and stay separate from runPermissions, which
166
+ // evaluates the function's own OR-groups against request data.
167
+ verifyScopes(funcConfig.scopes ?? funcMeta.scopes, session);
238
168
  // Evaluate the data from the lazy function
239
169
  const actualData = await data();
240
170
  // Validate and coerce data if schema is defined
@@ -247,21 +177,13 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
247
177
  // Validate request data against the defined schema, if any
248
178
  await validateSchema(resolvedSingletonServices.logger, resolvedSingletonServices.schema, inputSchemaName, actualData, packageName);
249
179
  }
250
- if (mergedInheritedPermissions.length > 0 ||
251
- wirePermissions ||
252
- funcMeta.permissions ||
253
- funcConfig.permissions) {
254
- await runPermissions(wireType, wireId, {
255
- wireInheritedPermissions: mergedInheritedPermissions,
256
- wirePermissions: wirePermissions,
257
- funcInheritedPermissions: funcMeta.permissions,
258
- funcPermissions: funcConfig.permissions,
259
- services: resolvedSingletonServices,
260
- wire: invocationWire,
261
- data: actualData,
262
- packageName,
263
- });
264
- }
180
+ await runPermissions({
181
+ funcPermissions: funcConfig.permissions,
182
+ services: resolvedSingletonServices,
183
+ wire: invocationWire,
184
+ data: actualData,
185
+ packageName,
186
+ });
265
187
  let wireServices;
266
188
  let invocationAuditLog;
267
189
  try {
@@ -333,6 +255,12 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
333
255
  else {
334
256
  invocationWire.audit = previousAudit;
335
257
  }
258
+ if (previousAddonNamespace === undefined) {
259
+ delete invocationWire.addonNamespace;
260
+ }
261
+ else {
262
+ invocationWire.addonNamespace = previousAddonNamespace;
263
+ }
336
264
  }
337
265
  }
338
266
  try {
@@ -1,4 +1,5 @@
1
1
  import type { CoreServices, CoreSingletonServices, CoreUserSession, CorePikkuMiddleware, PikkuWire, PickRequired } from '../types/core.types.js';
2
+ import type { PikkuRPC } from '../wirings/rpc/rpc-types.js';
2
3
  import type { StandardSchemaV1 } from '@standard-schema/spec';
3
4
  import type { PikkuError } from '../errors/error-handler.js';
4
5
  import type { CoreNodeConfig } from '../wirings/node/node.types.js';
@@ -15,7 +16,7 @@ export type ZodLike<T = any> = StandardSchemaV1<T, T>;
15
16
  * @template Services - The services type, defaults to `CoreServices`.
16
17
  * @template Wire - The wire type, defaults to `PikkuWire<In, Out>`.
17
18
  */
18
- export type CorePikkuFunction<In, Out, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession, Wire extends PikkuWire<In, Out, true, Session> = PikkuWire<In, Out, true, Session>> = (services: Services, data: In, wire: Wire) => Wire['channel'] extends null ? Promise<Out> : Promise<Out> | Promise<void>;
19
+ export type CorePikkuFunction<In, Out, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession, Wire extends PikkuWire<In, Out, true, Session, PikkuRPC, null, string> = PikkuWire<In, Out, true, Session>> = (services: Services, data: In, wire: Wire) => Wire['channel'] extends null ? Promise<Out> : Promise<Out> | Promise<void>;
19
20
  /**
20
21
  * Represents a core API function that can be used without a session.
21
22
  *
@@ -24,7 +25,7 @@ export type CorePikkuFunction<In, Out, Services extends CoreSingletonServices =
24
25
  * @template Services - The services type, defaults to `CoreServices`.
25
26
  * @template Wire - The wire type, defaults to `PikkuWire<In, Out>`.
26
27
  */
27
- export type CorePikkuFunctionSessionless<In, Out, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession, Wire extends PikkuWire<In, Out, false, Session, any, any, any> = PikkuWire<In, Out, false, Session, any, any, any>> = (services: Services, data: In, wire: Wire) => Wire['channel'] extends null ? Promise<Out> : Promise<Out> | Promise<void>;
28
+ export type CorePikkuFunctionSessionless<In, Out, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession, Wire extends PikkuWire<In, Out, false, Session, PikkuRPC, null, string> = PikkuWire<In, Out, false, Session>> = (services: Services, data: In, wire: Wire) => Wire['channel'] extends null ? Promise<Out> : Promise<Out> | Promise<void>;
28
29
  /**
29
30
  * Represents a function that checks permissions for a given operation.
30
31
  *
@@ -32,7 +33,7 @@ export type CorePikkuFunctionSessionless<In, Out, Services extends CoreSingleton
32
33
  * @template Services - The services type, defaults to `CoreServices`.
33
34
  * @template Session - The session type, defaults to `CoreUserSession`.
34
35
  */
35
- export type CorePikkuPermission<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, any, any, never, never> = PikkuWire<In, never, false, any, never, never, never>> = (services: Services, data: In, wire: Wire) => Promise<boolean>;
36
+ export type CorePikkuPermission<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, any, PikkuRPC, never, never> = PikkuWire<In, never, false, any, PikkuRPC, never, never>> = (services: Services, data: In, wire: Wire) => Promise<boolean>;
36
37
  /**
37
38
  * Configuration object for creating a permission with metadata
38
39
  *
@@ -40,7 +41,7 @@ export type CorePikkuPermission<In = any, Services extends CoreSingletonServices
40
41
  * @template Services - The services type, defaults to `CoreServices`.
41
42
  * @template Session - The session type, defaults to `CoreUserSession`.
42
43
  */
43
- export type CorePikkuPermissionConfig<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, any> = PikkuWire<In, never, false, any>> = {
44
+ export type CorePikkuPermissionConfig<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, any, PikkuRPC, never, never> = PikkuWire<In, never, false, any, PikkuRPC, never, never>> = {
44
45
  /** The permission function */
45
46
  func: CorePikkuPermission<In, Services, Wire>;
46
47
  /** Optional human-readable name for the permission */
@@ -73,7 +74,7 @@ export type CorePikkuPermissionConfig<In = any, Services extends CoreSingletonSe
73
74
  * })
74
75
  * ```
75
76
  */
76
- export declare const pikkuPermission: <In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PickRequired<PikkuWire<In, never, false, any>, "session"> = PickRequired<PikkuWire<In, never, false, any>, "session">>(permission: CorePikkuPermission<In, Services, Wire> | CorePikkuPermissionConfig<In, Services, Wire>) => CorePikkuPermission<In, Services, Wire>;
77
+ export declare const pikkuPermission: <In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PickRequired<PikkuWire<In, never, false, any, PikkuRPC, never, never>, "session"> = PickRequired<PikkuWire<In, never, false, any, PikkuRPC, never, never>, "session">>(permission: CorePikkuPermission<In, Services, Wire> | CorePikkuPermissionConfig<In, Services, Wire>) => CorePikkuPermission<In, Services, Wire>;
77
78
  /**
78
79
  * A factory function that takes input and returns a permission
79
80
  * Used when permissions need configuration/input parameters
@@ -82,7 +83,7 @@ export declare const pikkuPermission: <In = any, Services extends CoreSingletonS
82
83
  * @template Services - The services type, defaults to `CoreServices`.
83
84
  * @template Session - The session type, defaults to `CoreUserSession`.
84
85
  */
85
- export type CorePikkuPermissionFactory<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, any> = PikkuWire<In, never, false, any>> = (input: In) => CorePikkuPermission<any, Services, Wire>;
86
+ export type CorePikkuPermissionFactory<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, any, PikkuRPC, never, never> = PikkuWire<In, never, false, any, PikkuRPC, never, never>> = (input: In) => CorePikkuPermission<any, Services, Wire>;
86
87
  /**
87
88
  * Factory function for creating permission factories
88
89
  * Use this when your permission needs configuration/input parameters
@@ -134,7 +135,7 @@ export type CorePikkuAuthConfig<Services extends CoreSingletonServices = CoreSer
134
135
  };
135
136
  export declare const pikkuAuth: <Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession>(auth: CorePikkuAuth<Services, Session> | CorePikkuAuthConfig<Services, Session>) => CorePikkuPermission<any, Services, any>;
136
137
  export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> = Record<string, PikkuPermission | PikkuPermission[]> | undefined;
137
- export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any, any, any, any, any> | CorePikkuFunctionSessionless<any, any, any, any, any>, PikkuPermission extends CorePikkuPermission<any, any, any> = CorePikkuPermission<any>, PikkuMiddleware extends CorePikkuMiddleware<any, any> = CorePikkuMiddleware<any, any>, InputSchema extends StandardSchemaV1 | undefined = undefined, OutputSchema extends StandardSchemaV1 | undefined = undefined> = {
138
+ export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any, any, any, any, any> | CorePikkuFunctionSessionless<any, any, any, any, any>, PikkuPermission extends CorePikkuPermission<any, any, any> = CorePikkuPermission<any>, PikkuMiddleware extends CorePikkuMiddleware<any, any> = CorePikkuMiddleware<any, any>, InputSchema extends StandardSchemaV1 | undefined = undefined, OutputSchema extends StandardSchemaV1 | undefined = undefined, Scope extends string = string> = {
138
139
  /** Short human-readable name (e.g. "Create Todo") */
139
140
  title?: string;
140
141
  /** Longer-form description of what the function does */
@@ -161,6 +162,15 @@ export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any,
161
162
  approvalDescription?: any;
162
163
  func: PikkuFunction;
163
164
  auth?: boolean;
165
+ /**
166
+ * Scopes the session must hold to run this function. All of them are
167
+ * required (AND), and they are checked before `permissions` — unlike
168
+ * permissions, which OR together, a scope can only narrow access.
169
+ *
170
+ * Narrowed to the generated `ScopeId` union in a project's own
171
+ * `pikku-types.gen.ts`, so an undeclared scope is a compile error.
172
+ */
173
+ scopes?: Scope[];
164
174
  permissions?: CorePermissionGroup<PikkuPermission>;
165
175
  middleware?: PikkuMiddleware[];
166
176
  input?: InputSchema;
package/dist/index.d.ts CHANGED
@@ -36,6 +36,7 @@ export type { SessionService } from './services/user-session-service.js';
36
36
  export { NoopAuditService, createInvocationAudit, resolveAuditActorFromWire, resolveAuditConfig, } from './services/audit-service.js';
37
37
  export type { AuditActor, AuditConfig, AuditDurability, AuditEvent, AuditEventBatch, AuditLog, AuditLogWriteInput, AuditOutcome, AuditService, AuditSource, ResolvedAuditConfig, } from './services/audit-service.js';
38
38
  export type { AIAgentRunnerService, AIEmbedManyParams, AIEmbedManyResult, AIEmbedParams, AIEmbedResult, AIGenerateImageParams, AIGenerateImagePrompt, AIGenerateImageResult, AIGenerateSpeechParams, AIGenerateSpeechResult, AIProviderOptions, AIRerankParams, AIRerankResult, AITranscriptionParams, AITranscriptionResult, } from './services/ai-agent-runner-service.js';
39
+ export type { AIEmbeddingService } from './services/ai-embedding-service.js';
39
40
  export type { AIRunStateService } from './services/ai-run-state-service.js';
40
41
  export type { AIStorageService } from './services/ai-storage-service.js';
41
42
  export type { EmailsMeta, EmailTemplateMeta, EmailTemplateLocaleMeta, EmailTemplateAssets, MetaService, } from './services/meta-service.js';
@@ -44,9 +45,12 @@ export type { GraphNodeConfig } from './wirings/workflow/graph/workflow-graph.ty
44
45
  export { createGraph } from './wirings/workflow/graph/graph-node.js';
45
46
  export { wireAddon } from './wirings/rpc/wire-addon.js';
46
47
  export type { WireAddonConfig } from './wirings/rpc/wire-addon.js';
48
+ export { wireRemoteAddon } from './wirings/rpc/wire-remote-addon.js';
49
+ export type { WireRemoteAddonConfig, RemoteAddonAuth, } from './wirings/rpc/wire-remote-addon.js';
47
50
  export type { PikkuPackageState } from './types/state.types.js';
48
51
  export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
49
- export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, checkAuthPermissions, } from './permissions.js';
52
+ export { addGlobalPermission, checkAuthPermissions } from './permissions.js';
53
+ export { verifyScopes } from './scopes.js';
50
54
  export { isSerializable, stopSingletonServices, pikkuServerLifecycle, } from './utils.js';
51
55
  export { getSingletonServices, getCreateWireServices, setSingletonServices, } from './pikku-state.js';
52
56
  export { clearPikkuRuntimeState } from './test-utils.js';
package/dist/index.js CHANGED
@@ -15,8 +15,10 @@ export { PikkuError, isExpectedError } from './errors/error-handler.js';
15
15
  export { NoopAuditService, createInvocationAudit, resolveAuditActorFromWire, resolveAuditConfig, } from './services/audit-service.js';
16
16
  export { createGraph } from './wirings/workflow/graph/graph-node.js';
17
17
  export { wireAddon } from './wirings/rpc/wire-addon.js';
18
+ export { wireRemoteAddon } from './wirings/rpc/wire-remote-addon.js';
18
19
  export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
19
- export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, checkAuthPermissions, } from './permissions.js';
20
+ export { addGlobalPermission, checkAuthPermissions } from './permissions.js';
21
+ export { verifyScopes } from './scopes.js';
20
22
  export { isSerializable, stopSingletonServices, pikkuServerLifecycle, } from './utils.js';
21
23
  export { getSingletonServices, getCreateWireServices, setSingletonServices, } from './pikku-state.js';
22
24
  export { clearPikkuRuntimeState } from './test-utils.js';
@@ -21,5 +21,6 @@ export declare const authAPIKey: import("../types/core.types.js").CorePikkuMiddl
21
21
  logLevel?: import("../services/logger.js").LogLevel;
22
22
  secrets?: {};
23
23
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
24
+ webhook?: import("../services/webhook-service.js").WebhookServiceConfig;
24
25
  postgres?: import("../types/core.types.js").PostgresConfig;
25
26
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -53,5 +53,6 @@ export declare const authBearer: import("../types/core.types.js").CorePikkuMiddl
53
53
  logLevel?: import("../services/logger.js").LogLevel;
54
54
  secrets?: {};
55
55
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
56
+ webhook?: import("../services/webhook-service.js").WebhookServiceConfig;
56
57
  postgres?: import("../types/core.types.js").PostgresConfig;
57
58
  }>, CoreUserSession>;
@@ -36,5 +36,6 @@ export declare const authCookie: import("../types/core.types.js").CorePikkuMiddl
36
36
  logLevel?: import("../services/logger.js").LogLevel;
37
37
  secrets?: {};
38
38
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
39
+ webhook?: import("../services/webhook-service.js").WebhookServiceConfig;
39
40
  postgres?: import("../types/core.types.js").PostgresConfig;
40
41
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -43,5 +43,6 @@ export declare const cors: import("../types/core.types.js").CorePikkuMiddlewareF
43
43
  logLevel?: import("../services/logger.js").LogLevel;
44
44
  secrets?: {};
45
45
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
46
+ webhook?: import("../services/webhook-service.js").WebhookServiceConfig;
46
47
  postgres?: import("../types/core.types.js").PostgresConfig;
47
48
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -5,4 +5,4 @@ export { pikkuRemoteAuthMiddleware } from './remote-auth.js';
5
5
  export { cors } from './cors.js';
6
6
  export { telemetryOuter, telemetryInner } from './telemetry.js';
7
7
  export { addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, runMiddleware, } from '../middleware-runner.js';
8
- export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, } from '../permissions.js';
8
+ export { addGlobalPermission, addTagPermission } from '../permissions.js';
@@ -5,4 +5,4 @@ export { pikkuRemoteAuthMiddleware } from './remote-auth.js';
5
5
  export { cors } from './cors.js';
6
6
  export { telemetryOuter, telemetryInner } from './telemetry.js';
7
7
  export { addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, runMiddleware, } from '../middleware-runner.js';
8
- export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, } from '../permissions.js';
8
+ export { addGlobalPermission, addTagPermission } from '../permissions.js';
@@ -2,5 +2,6 @@ export declare const pikkuRemoteAuthMiddleware: import("../types/core.types.js")
2
2
  logLevel?: import("../services/logger.js").LogLevel;
3
3
  secrets?: {};
4
4
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
5
+ webhook?: import("../services/webhook-service.js").WebhookServiceConfig;
5
6
  postgres?: import("../types/core.types.js").PostgresConfig;
6
7
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -21,6 +21,7 @@ export declare const telemetryOuter: import("../types/core.types.js").CorePikkuM
21
21
  logLevel?: import("../services/logger.js").LogLevel;
22
22
  secrets?: {};
23
23
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
24
+ webhook?: import("../services/webhook-service.js").WebhookServiceConfig;
24
25
  postgres?: import("../types/core.types.js").PostgresConfig;
25
26
  }>, import("../types/core.types.js").CoreUserSession>;
26
27
  /**
@@ -45,5 +46,6 @@ export declare const telemetryInner: import("../types/core.types.js").CorePikkuM
45
46
  logLevel?: import("../services/logger.js").LogLevel;
46
47
  secrets?: {};
47
48
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
49
+ webhook?: import("../services/webhook-service.js").WebhookServiceConfig;
48
50
  postgres?: import("../types/core.types.js").PostgresConfig;
49
51
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -4,5 +4,6 @@ export declare const timeout: () => import("../types/core.types.js").CorePikkuMi
4
4
  logLevel?: import("../services/logger.js").LogLevel;
5
5
  secrets?: {};
6
6
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
7
+ webhook?: import("../services/webhook-service.js").WebhookServiceConfig;
7
8
  postgres?: import("../types/core.types.js").PostgresConfig;
8
9
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -1,36 +1,42 @@
1
- import type { CoreServices, CoreUserSession, PikkuWiringTypes, PermissionMetadata, PikkuWire } from './types/core.types.js';
1
+ import type { CoreServices, CoreUserSession, PermissionMetadata, PikkuWire } from './types/core.types.js';
2
2
  import type { CorePermissionGroup, CorePikkuPermission } from './function/functions.types.js';
3
+ export declare const clearPermissionsCache: () => void;
3
4
  /**
4
- * Registers tag-scoped permissions. Applies to any wiring that carries the
5
- * matching tag.
6
- *
7
- * For tree-shaking, wrap in a factory:
8
- * `export const x = () => addTagPermission('tag', [...])`
9
- *
10
- * @example
11
- * export const adminPermissions = () => addTagPermission('admin', [adminPermission])
12
- */
13
- export declare const addTagPermission: (tag: string, permissions: CorePermissionGroup | CorePikkuPermission[], packageName?: string | null) => CorePermissionGroup | CorePikkuPermission[];
14
- /**
15
- * Registers wire-agnostic global permissions. Runs at the top of every
16
- * wiring's permission resolution — before wire-, tag-, and function-level
17
- * entries — across all wire types.
5
+ * Registers wire-agnostic global permissions. Global permissions are an AND
6
+ * gate: every registered requirement must pass on every function invocation,
7
+ * regardless of transport. A requirement may be a single permission function
8
+ * or a permission group (in which case that group ORs internally, but the
9
+ * group as a whole must still pass).
18
10
  *
19
- * Resolution order: global wire tag function.
11
+ * Global permissions never satisfy a function's own `permissions` — the two
12
+ * are independent gates that both have to pass, so a broad global can only
13
+ * ever narrow access.
20
14
  *
21
15
  * @example
22
16
  * addGlobalPermission([signedInUser])
23
17
  */
24
18
  export declare const addGlobalPermission: (permissions: CorePermissionGroup | CorePikkuPermission[], packageName?: string | null) => CorePermissionGroup | CorePikkuPermission[];
25
- export declare const clearPermissionsCache: () => void;
26
19
  /**
27
- * Runs permission checks using combined permissions from all sources.
28
- * Combines permissions from wire and function levels, then validates them.
20
+ * @deprecated Tag-level permissions were removed in #972 — permissions are now
21
+ * function-scoped only (declare them on the function via `pikkuFunc({ permissions })`).
22
+ * This throwing stub exists solely so the pinned bootstrap CLI (which still
23
+ * generates an `addTagPermission` wrapper) can resolve the import at build time;
24
+ * it is never called. Delete once `PIKKU_CLI_VERSION` in the CLI build is bumped
25
+ * past the release that removes tag permissions.
26
+ */
27
+ export declare const addTagPermission: (_tag: string, _permissions: CorePermissionGroup | CorePikkuPermission[], _packageName?: string | null) => never;
28
+ /**
29
+ * Runs authorization for a function invocation. Two independent gates, both of
30
+ * which must pass:
31
+ *
32
+ * 1. Global permissions — AND. Every registered global requirement must pass.
33
+ * 2. Function permissions — OR. The function's own `permissions` group must be
34
+ * satisfied (see {@link verifyPermissions}).
35
+ *
36
+ * A passing global requirement never contributes to the function gate, so a
37
+ * broad global like `signedIn` can't satisfy an admin-only function.
29
38
  */
30
- export declare const runPermissions: (wireType: PikkuWiringTypes, uid: string, { wireInheritedPermissions, wirePermissions, funcInheritedPermissions, funcPermissions, services, wire, data, packageName, }: {
31
- wireInheritedPermissions?: PermissionMetadata[];
32
- wirePermissions?: CorePermissionGroup | CorePikkuPermission[];
33
- funcInheritedPermissions?: PermissionMetadata[];
39
+ export declare const runPermissions: ({ funcPermissions, services, wire, data, packageName, }: {
34
40
  funcPermissions?: CorePermissionGroup | CorePikkuPermission[];
35
41
  services: CoreServices;
36
42
  wire: PikkuWire<any, never, any, CoreUserSession, never, never, never>;
@@ -40,12 +46,13 @@ export declare const runPermissions: (wireType: PikkuWiringTypes, uid: string, {
40
46
  /**
41
47
  * Checks whether a session passes the auth checks (pikkuAuth only) for a
42
48
  * given function/agent. Skips pikkuPermission checks since those require
43
- * request data which isn't available at filter time.
49
+ * request data which isn't available at filter time. Global auth requirements
50
+ * are included so a filtered list honours app-wide auth.
44
51
  *
45
52
  * @param funcPermissions - The PermissionMetadata[] from function or agent metadata
46
53
  * @param session - The user's session
47
54
  * @param services - Singleton services
48
55
  * @param packageName - Optional package namespace
49
- * @returns true if the session passes all auth checks (or no auth checks exist)
56
+ * @returns true if the session passes the auth checks (or no auth checks exist)
50
57
  */
51
58
  export declare const checkAuthPermissions: (funcPermissions: PermissionMetadata[] | undefined, session: CoreUserSession, services: CoreServices, packageName?: string | null) => Promise<boolean>;