@pikku/core 0.12.63 → 0.12.66

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 (225) hide show
  1. package/CHANGELOG.md +368 -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 +42 -27
  24. package/dist/permissions.js +101 -203
  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/testing/service-tests.js +37 -0
  46. package/dist/types/core.types.d.ts +34 -6
  47. package/dist/types/state.types.d.ts +20 -2
  48. package/dist/utils/hmac.d.ts +16 -0
  49. package/dist/utils/hmac.js +26 -0
  50. package/dist/utils/safe-fetch.d.ts +51 -0
  51. package/dist/utils/safe-fetch.js +192 -0
  52. package/dist/wirings/ai-agent/ai-agent-agui.d.ts +84 -0
  53. package/dist/wirings/ai-agent/ai-agent-agui.js +305 -0
  54. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +105 -1
  55. package/dist/wirings/ai-agent/ai-agent-prepare.js +249 -11
  56. package/dist/wirings/ai-agent/ai-agent-runner.js +10 -1
  57. package/dist/wirings/ai-agent/ai-agent-stream.js +88 -16
  58. package/dist/wirings/ai-agent/ai-agent.types.d.ts +56 -2
  59. package/dist/wirings/ai-agent/index.d.ts +2 -1
  60. package/dist/wirings/ai-agent/index.js +2 -1
  61. package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
  62. package/dist/wirings/ai-agent/voice-input.js +5 -41
  63. package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
  64. package/dist/wirings/channel/channel-common.js +0 -1
  65. package/dist/wirings/channel/channel-handler.js +1 -4
  66. package/dist/wirings/channel/channel-middleware-runner.d.ts +12 -0
  67. package/dist/wirings/channel/channel-middleware-runner.js +34 -19
  68. package/dist/wirings/channel/channel.types.d.ts +2 -6
  69. package/dist/wirings/cli/cli-runner.js +3 -2
  70. package/dist/wirings/cli/cli.types.d.ts +1 -2
  71. package/dist/wirings/cli/command-parser.d.ts +2 -0
  72. package/dist/wirings/cli/command-parser.js +59 -2
  73. package/dist/wirings/credential/credential.types.d.ts +14 -0
  74. package/dist/wirings/credential/validate-credential-definitions.js +1 -0
  75. package/dist/wirings/gateway/gateway-runner.js +100 -50
  76. package/dist/wirings/gateway/gateway.types.d.ts +8 -5
  77. package/dist/wirings/http/http-routes.js +0 -3
  78. package/dist/wirings/http/http-runner.d.ts +9 -29
  79. package/dist/wirings/http/http-runner.js +9 -34
  80. package/dist/wirings/http/http.types.d.ts +4 -12
  81. package/dist/wirings/mcp/mcp-runner.js +0 -2
  82. package/dist/wirings/mcp/mcp.types.d.ts +5 -11
  83. package/dist/wirings/oauth2/index.d.ts +0 -3
  84. package/dist/wirings/oauth2/index.js +1 -2
  85. package/dist/wirings/rpc/addon-runner.d.ts +28 -0
  86. package/dist/wirings/rpc/addon-runner.js +173 -0
  87. package/dist/wirings/rpc/index.d.ts +5 -1
  88. package/dist/wirings/rpc/index.js +3 -1
  89. package/dist/wirings/rpc/remote-addon-auth.d.ts +26 -0
  90. package/dist/wirings/rpc/remote-addon-auth.js +43 -0
  91. package/dist/wirings/rpc/rpc-runner.d.ts +19 -0
  92. package/dist/wirings/rpc/rpc-runner.js +109 -3
  93. package/dist/wirings/rpc/rpc-types.d.ts +15 -1
  94. package/dist/wirings/rpc/wire-addon.js +9 -0
  95. package/dist/wirings/rpc/wire-remote-addon.d.ts +47 -0
  96. package/dist/wirings/rpc/wire-remote-addon.js +19 -0
  97. package/dist/wirings/scope/index.d.ts +3 -0
  98. package/dist/wirings/scope/index.js +2 -0
  99. package/dist/wirings/scope/scope.types.d.ts +39 -0
  100. package/dist/wirings/scope/scope.types.js +1 -0
  101. package/dist/wirings/scope/validate-scope-definitions.d.ts +16 -0
  102. package/dist/wirings/scope/validate-scope-definitions.js +76 -0
  103. package/dist/wirings/scope/wire-scope.d.ts +33 -0
  104. package/dist/wirings/scope/wire-scope.js +32 -0
  105. package/dist/wirings/secret/secret.types.d.ts +14 -0
  106. package/dist/wirings/secret/validate-secret-definitions.js +2 -0
  107. package/dist/wirings/variable/validate-variable-definitions.js +2 -0
  108. package/dist/wirings/variable/variable.types.d.ts +14 -0
  109. package/dist/wirings/workflow/dsl/index.d.ts +1 -1
  110. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +89 -7
  111. package/dist/wirings/workflow/graph/graph-node.d.ts +2 -0
  112. package/dist/wirings/workflow/graph/graph-runner.js +3 -0
  113. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +6 -0
  114. package/dist/wirings/workflow/graph/wire-workflow-graph.js +1 -0
  115. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +5 -0
  116. package/dist/wirings/workflow/index.d.ts +2 -2
  117. package/dist/wirings/workflow/index.js +1 -1
  118. package/dist/wirings/workflow/pikku-workflow-service.d.ts +59 -1
  119. package/dist/wirings/workflow/pikku-workflow-service.js +187 -0
  120. package/dist/wirings/workflow/workflow.types.d.ts +1 -3
  121. package/package.json +6 -1
  122. package/run-tests.sh +1 -0
  123. package/src/dev/hot-reload.ts +13 -68
  124. package/src/dev/module-runner.test.ts +169 -0
  125. package/src/dev/module-runner.ts +103 -0
  126. package/src/dev/reload-meta.test.ts +31 -2
  127. package/src/dev/reload-meta.ts +26 -0
  128. package/src/errors/errors.ts +24 -0
  129. package/src/function/function-runner.test.ts +276 -142
  130. package/src/function/function-runner.ts +52 -145
  131. package/src/function/functions.types.ts +28 -38
  132. package/src/index.ts +8 -6
  133. package/src/middleware/index.ts +1 -5
  134. package/src/permissions.test.ts +160 -373
  135. package/src/permissions.ts +137 -279
  136. package/src/pikku-state.ts +0 -2
  137. package/src/scopes.test.ts +167 -0
  138. package/src/scopes.ts +69 -0
  139. package/src/services/ai-embedding-service.ts +31 -0
  140. package/src/services/credential-wire-service.ts +10 -4
  141. package/src/services/index.ts +20 -0
  142. package/src/services/meta-service.ts +68 -3
  143. package/src/services/queue-webhook-service.test.ts +408 -0
  144. package/src/services/queue-webhook-service.ts +182 -0
  145. package/src/services/scope-service.ts +75 -0
  146. package/src/services/typed-secret-service.test.ts +35 -0
  147. package/src/services/typed-secret-service.ts +39 -4
  148. package/src/services/webhook-service.ts +180 -0
  149. package/src/services/workflow-service.ts +7 -0
  150. package/src/testing/service-tests.ts +49 -0
  151. package/src/types/core.types.ts +42 -14
  152. package/src/types/state.types.ts +23 -3
  153. package/src/utils/hmac.ts +27 -0
  154. package/src/utils/safe-fetch.test.ts +373 -0
  155. package/src/utils/safe-fetch.ts +213 -0
  156. package/src/wirings/ai-agent/ai-agent-agui.test.ts +1127 -0
  157. package/src/wirings/ai-agent/ai-agent-agui.ts +386 -0
  158. package/src/wirings/ai-agent/ai-agent-authorization.test.ts +204 -0
  159. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +483 -0
  160. package/src/wirings/ai-agent/ai-agent-prepare.ts +324 -13
  161. package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +207 -0
  162. package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
  163. package/src/wirings/ai-agent/ai-agent-runner.ts +28 -0
  164. package/src/wirings/ai-agent/ai-agent-stream.test.ts +301 -10
  165. package/src/wirings/ai-agent/ai-agent-stream.ts +122 -16
  166. package/src/wirings/ai-agent/ai-agent.types.ts +57 -0
  167. package/src/wirings/ai-agent/index.ts +5 -0
  168. package/src/wirings/ai-agent/voice-input.test.ts +90 -0
  169. package/src/wirings/ai-agent/voice-input.ts +9 -40
  170. package/src/wirings/channel/channel-common.ts +0 -1
  171. package/src/wirings/channel/channel-handler.ts +1 -9
  172. package/src/wirings/channel/channel-middleware-runner.test.ts +28 -2
  173. package/src/wirings/channel/channel-middleware-runner.ts +39 -27
  174. package/src/wirings/channel/channel.types.ts +0 -6
  175. package/src/wirings/cli/cli-runner.ts +4 -2
  176. package/src/wirings/cli/cli.types.ts +0 -2
  177. package/src/wirings/cli/command-parser.test.ts +130 -0
  178. package/src/wirings/cli/command-parser.ts +80 -2
  179. package/src/wirings/credential/credential.types.ts +14 -0
  180. package/src/wirings/credential/validate-credential-definitions.ts +1 -0
  181. package/src/wirings/gateway/gateway-authorization.test.ts +444 -0
  182. package/src/wirings/gateway/gateway-runner.ts +114 -68
  183. package/src/wirings/gateway/gateway.types.ts +7 -9
  184. package/src/wirings/http/http-routes.ts +0 -3
  185. package/src/wirings/http/http-runner.test.ts +1 -11
  186. package/src/wirings/http/http-runner.ts +16 -39
  187. package/src/wirings/http/http.types.ts +7 -14
  188. package/src/wirings/mcp/mcp-runner.ts +0 -2
  189. package/src/wirings/mcp/mcp.types.ts +3 -11
  190. package/src/wirings/oauth2/index.ts +0 -3
  191. package/src/wirings/rpc/addon-runner.ts +233 -0
  192. package/src/wirings/rpc/index.ts +17 -1
  193. package/src/wirings/rpc/remote-addon-auth.ts +69 -0
  194. package/src/wirings/rpc/rpc-runner.test.ts +319 -16
  195. package/src/wirings/rpc/rpc-runner.ts +155 -3
  196. package/src/wirings/rpc/rpc-types.ts +16 -5
  197. package/src/wirings/rpc/wire-addon.test.ts +6 -3
  198. package/src/wirings/rpc/wire-addon.ts +9 -0
  199. package/src/wirings/rpc/wire-remote-addon.ts +57 -0
  200. package/src/wirings/scope/index.ts +14 -0
  201. package/src/wirings/scope/scope.test.ts +135 -0
  202. package/src/wirings/scope/scope.types.ts +44 -0
  203. package/src/wirings/scope/validate-scope-definitions.ts +110 -0
  204. package/src/wirings/scope/wire-scope.ts +34 -0
  205. package/src/wirings/secret/secret.types.ts +14 -0
  206. package/src/wirings/secret/validate-secret-definitions.ts +2 -0
  207. package/src/wirings/variable/validate-variable-definitions.ts +2 -0
  208. package/src/wirings/variable/variable.types.ts +14 -0
  209. package/src/wirings/workflow/dsl/index.ts +4 -0
  210. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +97 -6
  211. package/src/wirings/workflow/graph/graph-node.ts +2 -0
  212. package/src/wirings/workflow/graph/graph-runner.test.ts +58 -0
  213. package/src/wirings/workflow/graph/graph-runner.ts +3 -0
  214. package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
  215. package/src/wirings/workflow/graph/workflow-graph.types.ts +5 -0
  216. package/src/wirings/workflow/index.ts +5 -0
  217. package/src/wirings/workflow/pikku-workflow-service.test.ts +341 -0
  218. package/src/wirings/workflow/pikku-workflow-service.ts +272 -0
  219. package/src/wirings/workflow/workflow-on-error.test.ts +154 -0
  220. package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
  221. package/src/wirings/workflow/workflow.types.ts +4 -2
  222. package/tsconfig.tsbuildinfo +1 -1
  223. package/src/wirings/oauth2/oauth2-client.test.ts +0 -930
  224. package/src/wirings/oauth2/oauth2-client.ts +0 -331
  225. package/src/wirings/oauth2/oauth2-routes.ts +0 -234
@@ -1,9 +1,9 @@
1
1
  export type PikkuRPC<
2
- Invoke extends Function = any,
3
- Remote extends Function = any,
4
- startWorkflow extends Function = any,
5
- AgentRun extends Function = any,
6
- AgentStream extends Function = any,
2
+ Invoke extends (...args: any[]) => any = (...args: any[]) => any,
3
+ Remote extends (...args: any[]) => any = (...args: any[]) => any,
4
+ startWorkflow extends (...args: any[]) => any = (...args: any[]) => any,
5
+ AgentRun extends (...args: any[]) => any = (...args: any[]) => any,
6
+ AgentStream extends (...args: any[]) => any = (...args: any[]) => any,
7
7
  > = {
8
8
  depth: number
9
9
  global: boolean
@@ -52,5 +52,16 @@ export interface ResolvedFunction {
52
52
  auth?: boolean
53
53
  tags?: string[]
54
54
  rpcEndpoint?: string
55
+ secretOverrides?: Record<string, string>
56
+ variableOverrides?: Record<string, string>
57
+ credentialOverrides?: Record<string, string>
58
+ /** Set by `wireRemoteAddon`: dispatch this namespace's RPCs over HTTP */
59
+ remote?: boolean
60
+ serverUrl?: string | ((services: any) => string | Promise<string>)
61
+ remoteAuth?:
62
+ | { credentialId: string }
63
+ | { secretId: string }
64
+ | { resolve: (services: any, wire: any) => string | Promise<string> }
65
+ remoteName?: (fn: string) => string
55
66
  }
56
67
  }
@@ -17,9 +17,9 @@ describe('wireAddon', () => {
17
17
  auth: true,
18
18
  mcp: true,
19
19
  tags: ['payments', 'billing'],
20
- secretOverrides: { apiKey: 'secretName' },
21
- variableOverrides: { region: 'eu-west-1' },
22
- credentialOverrides: { oauth: 'credentialName' },
20
+ secretOverrides: { apiKey: 'STRIPE_API_KEY' },
21
+ variableOverrides: { region: 'AWS_REGION' },
22
+ credentialOverrides: { oauth: 'stripeOAuth' },
23
23
  })
24
24
 
25
25
  assert.deepEqual(pikkuState(null, 'addons', 'packages').get('stripe'), {
@@ -27,6 +27,9 @@ describe('wireAddon', () => {
27
27
  rpcEndpoint: 'https://rpc.example.com',
28
28
  auth: true,
29
29
  tags: ['payments', 'billing'],
30
+ secretOverrides: { apiKey: 'STRIPE_API_KEY' },
31
+ variableOverrides: { region: 'AWS_REGION' },
32
+ credentialOverrides: { oauth: 'stripeOAuth' },
30
33
  })
31
34
  })
32
35
 
@@ -18,5 +18,14 @@ export const wireAddon = (config: WireAddonConfig): void => {
18
18
  rpcEndpoint: config.rpcEndpoint,
19
19
  auth: config.auth,
20
20
  tags: config.tags,
21
+ ...(config.secretOverrides
22
+ ? { secretOverrides: config.secretOverrides }
23
+ : {}),
24
+ ...(config.variableOverrides
25
+ ? { variableOverrides: config.variableOverrides }
26
+ : {}),
27
+ ...(config.credentialOverrides
28
+ ? { credentialOverrides: config.credentialOverrides }
29
+ : {}),
21
30
  })
22
31
  }
@@ -0,0 +1,57 @@
1
+ import { pikkuState } from '../../pikku-state.js'
2
+ import type { CoreServices, PikkuWire } from '../../types/core.types.js'
3
+
4
+ /**
5
+ * How the consumer supplies the token the hosted addon's auth expects.
6
+ *
7
+ * This is a CLIENT authenticating to a hosted library — NOT pikku's trusted
8
+ * machine-to-machine mesh (which uses `PIKKU_REMOTE_SECRET`). The consumer binds
9
+ * the addon's declared auth requirement to a local source:
10
+ * - `credentialId` — per-user credential, resolved via `wire.getCredential(id)`
11
+ * - `secretId` — platform key, resolved via the secrets service
12
+ * - `resolve` — custom escape hatch
13
+ * Omit entirely when the addon declares its remote surface is public.
14
+ *
15
+ * The value is sent `Authorization: Bearer <token>` by default; a non-default
16
+ * header is declared once by the addon (its meta), never chosen here.
17
+ */
18
+ export type RemoteAddonAuth =
19
+ | { credentialId: string }
20
+ | { secretId: string }
21
+ | { resolve: (services: CoreServices, wire: PikkuWire) => string | Promise<string> }
22
+
23
+ export type WireRemoteAddonConfig = {
24
+ /** Consumer-facing namespace, e.g. `registry` → `rpc('registry:getOpenApi')` */
25
+ name: string
26
+ /**
27
+ * The addon package. Installed as a **devDependency** (types only — its
28
+ * handlers run on the host); `pikku verify` enforces this.
29
+ */
30
+ package: string
31
+ /** Base URL of the host serving the addon's remote surface. */
32
+ serverUrl: string | ((services: CoreServices) => string | Promise<string>)
33
+ /** Bind the addon's declared auth to a local source. Omit if the surface is public. */
34
+ auth?: RemoteAddonAuth
35
+ /** Map a consumer-facing fn name → the remote fn name, when they differ (rare). */
36
+ remoteName?: (fn: string) => string
37
+ tags?: string[]
38
+ }
39
+
40
+ /**
41
+ * Consume a hosted addon's `remote: true` RPCs transparently over HTTP.
42
+ *
43
+ * Unlike `wireAddon` (which bundles the addon's functions in-process, a
44
+ * production dependency), `wireRemoteAddon` dispatches `rpc('name:fn')` to the
45
+ * host at `serverUrl`, fully typed — the addon ships as a devDependency (types
46
+ * only). See {@link RemoteAddonAuth} for how auth is bound.
47
+ */
48
+ export const wireRemoteAddon = (config: WireRemoteAddonConfig): void => {
49
+ pikkuState(null, 'addons', 'packages').set(config.name, {
50
+ package: config.package,
51
+ tags: config.tags,
52
+ remote: true,
53
+ serverUrl: config.serverUrl,
54
+ ...(config.auth ? { remoteAuth: config.auth } : {}),
55
+ ...(config.remoteName ? { remoteName: config.remoteName } : {}),
56
+ })
57
+ }
@@ -0,0 +1,14 @@
1
+ export { wireScope } from './wire-scope.js'
2
+ export {
3
+ flattenScopeDefinitions,
4
+ validateAndBuildScopeDefinitionsMeta,
5
+ } from './validate-scope-definitions.js'
6
+ export type {
7
+ CoreScopes,
8
+ CoreScopeNode,
9
+ FlatScope,
10
+ ScopeDefinitionMeta,
11
+ ScopeDefinitionsMeta,
12
+ ScopeDefinitions,
13
+ ScopeNodeMeta,
14
+ } from './scope.types.js'
@@ -0,0 +1,135 @@
1
+ import { describe, test } from 'node:test'
2
+ import * as assert from 'node:assert'
3
+ import {
4
+ flattenScopeDefinitions,
5
+ validateAndBuildScopeDefinitionsMeta,
6
+ } from './validate-scope-definitions.js'
7
+ import type { ScopeDefinitions } from './scope.types.js'
8
+
9
+ describe('flattenScopeDefinitions', () => {
10
+ test('a flat scope yields itself', () => {
11
+ const definitions: ScopeDefinitions = [
12
+ { name: 'admin', description: 'Administration' },
13
+ ]
14
+
15
+ assert.deepEqual(flattenScopeDefinitions(definitions), [
16
+ { id: 'admin', description: 'Administration' },
17
+ ])
18
+ })
19
+
20
+ test('nested scopes yield every intermediate level', () => {
21
+ const definitions: ScopeDefinitions = [
22
+ {
23
+ name: 'admin',
24
+ scopes: {
25
+ invoices: {
26
+ description: 'Invoice management',
27
+ scopes: { create: { description: 'Create invoices' } },
28
+ },
29
+ },
30
+ },
31
+ ]
32
+
33
+ assert.deepEqual(flattenScopeDefinitions(definitions), [
34
+ { id: 'admin', description: undefined },
35
+ { id: 'admin:invoices', description: 'Invoice management' },
36
+ { id: 'admin:invoices:create', description: 'Create invoices' },
37
+ ])
38
+ })
39
+
40
+ test('siblings are flattened depth-first', () => {
41
+ const definitions: ScopeDefinitions = [
42
+ {
43
+ name: 'billing',
44
+ scopes: { read: {}, write: {} },
45
+ },
46
+ ]
47
+
48
+ assert.deepEqual(
49
+ flattenScopeDefinitions(definitions).map((s) => s.id),
50
+ ['billing', 'billing:read', 'billing:write']
51
+ )
52
+ })
53
+
54
+ test('several definitions are flattened together', () => {
55
+ const definitions: ScopeDefinitions = [
56
+ { name: 'admin', scopes: { users: {} } },
57
+ { name: 'billing' },
58
+ ]
59
+
60
+ assert.deepEqual(
61
+ flattenScopeDefinitions(definitions).map((s) => s.id),
62
+ ['admin', 'admin:users', 'billing']
63
+ )
64
+ })
65
+ })
66
+
67
+ describe('validateAndBuildScopeDefinitionsMeta', () => {
68
+ test('keys definitions by name', () => {
69
+ const meta = validateAndBuildScopeDefinitionsMeta([
70
+ { name: 'admin', description: 'Administration' },
71
+ ])
72
+
73
+ assert.equal(meta.admin!.name, 'admin')
74
+ assert.equal(meta.admin!.description, 'Administration')
75
+ })
76
+
77
+ test('an identical duplicate is tolerated', () => {
78
+ const meta = validateAndBuildScopeDefinitionsMeta([
79
+ { name: 'admin', scopes: { users: {} } },
80
+ { name: 'admin', scopes: { users: {} } },
81
+ ])
82
+
83
+ assert.equal(Object.keys(meta).length, 1)
84
+ })
85
+
86
+ test('a conflicting duplicate throws and names both files', () => {
87
+ assert.throws(
88
+ () =>
89
+ validateAndBuildScopeDefinitionsMeta([
90
+ { name: 'admin', scopes: { users: {} }, sourceFile: 'a.ts' },
91
+ { name: 'admin', scopes: { invoices: {} }, sourceFile: 'b.ts' },
92
+ ]),
93
+ (err: Error) => {
94
+ assert.match(err.message, /admin/)
95
+ assert.match(err.message, /a\.ts/)
96
+ assert.match(err.message, /b\.ts/)
97
+ return true
98
+ }
99
+ )
100
+ })
101
+
102
+ test('rejects a name containing the separator', () => {
103
+ assert.throws(
104
+ () => validateAndBuildScopeDefinitionsMeta([{ name: 'admin:users' }]),
105
+ /separator/i
106
+ )
107
+ })
108
+
109
+ test('rejects a nested key containing the separator', () => {
110
+ assert.throws(
111
+ () =>
112
+ validateAndBuildScopeDefinitionsMeta([
113
+ { name: 'admin', scopes: { 'a:b': {} } },
114
+ ]),
115
+ /separator/i
116
+ )
117
+ })
118
+
119
+ test('rejects a wildcard as a declared name', () => {
120
+ assert.throws(
121
+ () => validateAndBuildScopeDefinitionsMeta([{ name: '*' }]),
122
+ /wildcard/i
123
+ )
124
+ })
125
+
126
+ test('rejects a wildcard nested inside a scope', () => {
127
+ assert.throws(
128
+ () =>
129
+ validateAndBuildScopeDefinitionsMeta([
130
+ { name: 'admin', scopes: { '*': {} } },
131
+ ]),
132
+ /wildcard/i
133
+ )
134
+ })
135
+ })
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A node within a scope tree. Every node is itself a grantable scope — given
3
+ * `admin` containing `invoices` containing `create`, all three of `admin`,
4
+ * `admin:invoices` and `admin:invoices:create` are valid scope ids.
5
+ */
6
+ export type CoreScopeNode = {
7
+ /** Short human-readable name, e.g. "Administration". */
8
+ displayName?: string
9
+ /** Longer-form description, surfaced in the console when granting. */
10
+ description?: string
11
+ /** Nested scopes, keyed by their segment. */
12
+ scopes?: Record<string, CoreScopeNode>
13
+ }
14
+
15
+ /**
16
+ * Scope trees to declare, keyed by their root segment. A root is just a node —
17
+ * it is named by its key, exactly like every node beneath it. A key must not
18
+ * contain `:` or be `*`.
19
+ */
20
+ export type CoreScopes = Record<string, CoreScopeNode>
21
+
22
+ export type ScopeNodeMeta = {
23
+ displayName?: string
24
+ description?: string
25
+ scopes?: Record<string, ScopeNodeMeta>
26
+ }
27
+
28
+ export type ScopeDefinitionMeta = {
29
+ name: string
30
+ displayName?: string
31
+ description?: string
32
+ scopes?: Record<string, ScopeNodeMeta>
33
+ sourceFile?: string
34
+ }
35
+
36
+ export type ScopeDefinitions = ScopeDefinitionMeta[]
37
+ export type ScopeDefinitionsMeta = Record<string, ScopeDefinitionMeta>
38
+
39
+ /** A single scope, flattened out of the declared tree. */
40
+ export type FlatScope = {
41
+ /** Colon-delimited id, e.g. `admin:invoices:create`. */
42
+ id: string
43
+ description?: string
44
+ }
@@ -0,0 +1,110 @@
1
+ import type {
2
+ FlatScope,
3
+ ScopeDefinitions,
4
+ ScopeDefinitionsMeta,
5
+ ScopeNodeMeta,
6
+ } from './scope.types.js'
7
+
8
+ const SEPARATOR = ':'
9
+ const WILDCARD = '*'
10
+
11
+ const assertSegment = (segment: string, scopeName: string): void => {
12
+ if (segment.includes(SEPARATOR)) {
13
+ throw new Error(
14
+ `Scope segment '${segment}' in '${scopeName}' contains the '${SEPARATOR}' separator. ` +
15
+ `Nest scopes with the 'scopes' property instead of embedding '${SEPARATOR}' in a name.`
16
+ )
17
+ }
18
+ if (segment === WILDCARD) {
19
+ throw new Error(
20
+ `Scope segment '${segment}' in '${scopeName}' is the wildcard. ` +
21
+ `'${WILDCARD}' is reserved for granting a scope and its descendants, and cannot be declared.`
22
+ )
23
+ }
24
+ if (segment.length === 0) {
25
+ throw new Error(`Scope '${scopeName}' contains an empty segment.`)
26
+ }
27
+ }
28
+
29
+ const assertNodesValid = (
30
+ nodes: Record<string, ScopeNodeMeta> | undefined,
31
+ scopeName: string
32
+ ): void => {
33
+ for (const [segment, node] of Object.entries(nodes ?? {})) {
34
+ assertSegment(segment, scopeName)
35
+ assertNodesValid(node.scopes, scopeName)
36
+ }
37
+ }
38
+
39
+ const flattenNodes = (
40
+ nodes: Record<string, ScopeNodeMeta> | undefined,
41
+ prefix: string,
42
+ out: FlatScope[]
43
+ ): void => {
44
+ for (const [segment, node] of Object.entries(nodes ?? {})) {
45
+ const id = `${prefix}${SEPARATOR}${segment}`
46
+ out.push({ id, description: node.description })
47
+ flattenNodes(node.scopes, id, out)
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Flattens declared scope trees into the full list of grantable scope ids,
53
+ * depth-first. Every node is emitted, including intermediate ones.
54
+ *
55
+ * Used by codegen to build the `ScopeId` union, and by a ScopeService to sync
56
+ * the declared set into its store.
57
+ */
58
+ export const flattenScopeDefinitions = (
59
+ definitions: ScopeDefinitions
60
+ ): FlatScope[] => {
61
+ const out: FlatScope[] = []
62
+ for (const def of definitions) {
63
+ out.push({ id: def.name, description: def.description })
64
+ flattenNodes(def.scopes, def.name, out)
65
+ }
66
+ return out
67
+ }
68
+
69
+ /**
70
+ * Validates declared scopes and keys them by name.
71
+ *
72
+ * Definitions sharing a name must be identical; a conflicting redeclaration is
73
+ * a hard error naming both source files.
74
+ */
75
+ export function validateAndBuildScopeDefinitionsMeta(
76
+ definitions: ScopeDefinitions
77
+ ): ScopeDefinitionsMeta {
78
+ const meta: ScopeDefinitionsMeta = {}
79
+
80
+ for (const def of definitions) {
81
+ assertSegment(def.name, def.name)
82
+ assertNodesValid(def.scopes, def.name)
83
+
84
+ const existing = meta[def.name]
85
+ if (existing) {
86
+ const sameShape =
87
+ JSON.stringify(existing.scopes ?? {}) ===
88
+ JSON.stringify(def.scopes ?? {})
89
+ if (!sameShape) {
90
+ throw new Error(
91
+ `Scope '${def.name}' is declared with different nested scopes.\n` +
92
+ ` First declaration: ${existing.sourceFile ?? 'unknown'}\n` +
93
+ ` Second declaration: ${def.sourceFile ?? 'unknown'}\n` +
94
+ `Scopes sharing a name must declare the same tree.`
95
+ )
96
+ }
97
+ continue
98
+ }
99
+
100
+ meta[def.name] = {
101
+ name: def.name,
102
+ displayName: def.displayName,
103
+ description: def.description,
104
+ scopes: def.scopes,
105
+ sourceFile: def.sourceFile,
106
+ }
107
+ }
108
+
109
+ return meta
110
+ }
@@ -0,0 +1,34 @@
1
+ import type { CoreScopes } from './scope.types.js'
2
+
3
+ /**
4
+ * No-op function for declaring scopes.
5
+ * This exists purely for TypeScript type checking and will be tree-shaken.
6
+ * The CLI extracts metadata via AST parsing and generates a `ScopeId` union,
7
+ * so a function referencing an undeclared scope fails the build.
8
+ *
9
+ * Scopes are keyed by segment at every level: a scope is named by its key, and
10
+ * its value describes it. Every node is grantable — the declaration below
11
+ * yields `admin`, `admin:invoices`, `admin:invoices:create`,
12
+ * `admin:invoices:void` and `billing`.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * wireScope({
17
+ * admin: {
18
+ * displayName: 'Administration',
19
+ * description: 'Administrative access',
20
+ * scopes: {
21
+ * invoices: {
22
+ * description: 'Invoice management',
23
+ * scopes: {
24
+ * create: { description: 'Create invoices' },
25
+ * void: { description: 'Void invoices' },
26
+ * },
27
+ * },
28
+ * },
29
+ * },
30
+ * billing: {},
31
+ * })
32
+ * ```
33
+ */
34
+ export const wireScope = (_config: CoreScopes): void => {}
@@ -4,6 +4,13 @@ export type CoreSecret<T = unknown> = {
4
4
  description?: string
5
5
  secretId: string
6
6
  schema: T
7
+ /**
8
+ * Link to documentation explaining how to obtain this value — a provider's
9
+ * API-key page, a setup guide, an internal runbook. Surfaced by consoles and
10
+ * deploy UIs so a user facing a missing value has somewhere to go instead of
11
+ * an opaque identifier.
12
+ */
13
+ docsUrl?: string
7
14
  /**
8
15
  * Optional rotation cadence for this secret, e.g. '1d', '30day', '1w'.
9
16
  * Stored in the generated secrets metadata so consumers can tell when a
@@ -27,6 +34,13 @@ export type SecretDefinitionMeta = {
27
34
  description?: string
28
35
  secretId: string
29
36
  schema?: Record<string, unknown> | string
37
+ /**
38
+ * Link to documentation explaining how to obtain this value — a provider's
39
+ * API-key page, a setup guide, an internal runbook. Surfaced by consoles and
40
+ * deploy UIs so a user facing a missing value has somewhere to go instead of
41
+ * an opaque identifier.
42
+ */
43
+ docsUrl?: string
30
44
  oauth2?: OAuth2CredentialConfig
31
45
  rotationPeriod?: string
32
46
  sourceFile?: string
@@ -58,6 +58,7 @@ export function validateAndBuildSecretDefinitionsMeta(
58
58
  schema: def.schema,
59
59
  oauth2: def.oauth2,
60
60
  rotationPeriod: def.rotationPeriod,
61
+ docsUrl: def.docsUrl,
61
62
  sourceFile: def.sourceFile,
62
63
  }
63
64
  }
@@ -75,6 +76,7 @@ export function validateAndBuildSecretDefinitionsMeta(
75
76
  schema: def.schema,
76
77
  oauth2: def.oauth2,
77
78
  rotationPeriod: def.rotationPeriod,
79
+ docsUrl: def.docsUrl,
78
80
  sourceFile: def.sourceFile,
79
81
  }
80
82
  }
@@ -45,6 +45,7 @@ export function validateAndBuildVariableDefinitionsMeta(
45
45
  description: def.description,
46
46
  variableId: def.variableId,
47
47
  schema: def.schema,
48
+ docsUrl: def.docsUrl,
48
49
  sourceFile: def.sourceFile,
49
50
  }
50
51
  }
@@ -60,6 +61,7 @@ export function validateAndBuildVariableDefinitionsMeta(
60
61
  description: def.description,
61
62
  variableId: def.variableId,
62
63
  schema: def.schema,
64
+ docsUrl: def.docsUrl,
63
65
  sourceFile: def.sourceFile,
64
66
  }
65
67
  }
@@ -4,6 +4,13 @@ export type CoreVariable<T = unknown> = {
4
4
  description?: string
5
5
  variableId: string
6
6
  schema: T
7
+ /**
8
+ * Link to documentation explaining how to obtain this value — a provider's
9
+ * API-key page, a setup guide, an internal runbook. Surfaced by consoles and
10
+ * deploy UIs so a user facing a missing value has somewhere to go instead of
11
+ * an opaque identifier.
12
+ */
13
+ docsUrl?: string
7
14
  }
8
15
 
9
16
  export type VariableDefinitionMeta = {
@@ -12,6 +19,13 @@ export type VariableDefinitionMeta = {
12
19
  description?: string
13
20
  variableId: string
14
21
  schema?: Record<string, unknown> | string
22
+ /**
23
+ * Link to documentation explaining how to obtain this value — a provider's
24
+ * API-key page, a setup guide, an internal runbook. Surfaced by consoles and
25
+ * deploy UIs so a user facing a missing value has somewhere to go instead of
26
+ * an opaque identifier.
27
+ */
28
+ docsUrl?: string
15
29
  sourceFile?: string
16
30
  }
17
31
 
@@ -9,6 +9,9 @@ export type {
9
9
  WorkflowWireDoInline,
10
10
  WorkflowWireSleep,
11
11
  WorkflowWireSuspend,
12
+ WorkflowWireApproval,
13
+ WorkflowApprovalOptions,
14
+ ApprovalOutcome,
12
15
  InputSource,
13
16
  OutputBinding,
14
17
  RpcStepMeta,
@@ -22,6 +25,7 @@ export type {
22
25
  SleepStepMeta,
23
26
  CancelStepMeta,
24
27
  SuspendStepMeta,
28
+ ApprovalStepMeta,
25
29
  SwitchCaseMeta,
26
30
  SwitchStepMeta,
27
31
  FilterStepMeta,