@pikku/core 0.12.92 → 0.12.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/dist/errors/error-handler.d.ts +11 -0
- package/dist/errors/error-handler.js +8 -0
- package/dist/errors/serialized-error.d.ts +3 -0
- package/dist/function/function-runner.d.ts +1 -1
- package/dist/function/function-runner.js +3 -3
- package/dist/function/functions.types.d.ts +33 -21
- package/dist/middleware/auth-apikey.d.ts +7 -0
- package/dist/middleware/auth-apikey.js +6 -0
- package/dist/middleware/auth-bearer.d.ts +3 -0
- package/dist/middleware/auth-bearer.js +2 -0
- package/dist/middleware/auth-cookie.d.ts +9 -0
- package/dist/middleware/auth-cookie.js +6 -4
- package/dist/middleware/cors.d.ts +8 -0
- package/dist/middleware/cors.js +2 -0
- package/dist/middleware/middleware-factories.d.ts +14 -0
- package/dist/middleware/middleware-factories.js +14 -0
- package/dist/services/local-secrets.js +1 -1
- package/dist/services/personas-service.d.ts +4 -0
- package/dist/services/personas-service.js +4 -0
- package/dist/services/secret-service.d.ts +0 -1
- package/dist/testing/service-tests/secret-service-tests.js +1 -1
- package/dist/types/core.types.d.ts +5 -0
- package/dist/wirings/addon/wire-addon.d.ts +17 -15
- package/dist/wirings/addon/wire-addon.js +6 -0
- package/dist/wirings/addon/wire-remote-addon.d.ts +4 -0
- package/dist/wirings/addon/wire-remote-addon.js +4 -0
- package/dist/wirings/agent/agent-prepare.js +2 -2
- package/dist/wirings/agent/agent.types.d.ts +31 -27
- package/dist/wirings/agent-scorer/agent-scorer.d.ts +4 -0
- package/dist/wirings/agent-scorer/agent-scorer.js +4 -0
- package/dist/wirings/channel/channel.types.d.ts +11 -0
- package/dist/wirings/cli/cli.types.d.ts +43 -1
- package/dist/wirings/cli/define-cli-commands.d.ts +6 -1
- package/dist/wirings/cli/define-cli-commands.js +6 -1
- package/dist/wirings/credential/credential.types.d.ts +7 -0
- package/dist/wirings/gateway/gateway.types.d.ts +23 -0
- package/dist/wirings/http/http.types.d.ts +51 -37
- package/dist/wirings/mcp/mcp.types.d.ts +27 -0
- package/dist/wirings/persona/index.d.ts +1 -0
- package/dist/wirings/persona/index.js +1 -0
- package/dist/wirings/persona/persona-app-scopes.d.ts +41 -0
- package/dist/wirings/persona/persona-app-scopes.js +61 -0
- package/dist/wirings/queue/queue.types.d.ts +6 -0
- package/dist/wirings/scheduler/scheduler.types.d.ts +5 -0
- package/dist/wirings/secret/secret.types.d.ts +9 -14
- package/dist/wirings/secret/secret.types.js +7 -0
- package/dist/wirings/trigger/trigger.types.d.ts +4 -1
- package/dist/wirings/variable/variable.types.d.ts +14 -13
- package/dist/wirings/variable/variable.types.js +7 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +7 -0
- package/dist/wirings/workflow/scenario-cookie-jar.d.ts +6 -0
- package/dist/wirings/workflow/scenario-cookie-jar.js +6 -0
- package/dist/wirings/workflow/scenario-poll.d.ts +6 -0
- package/dist/wirings/workflow/scenario-poll.js +6 -0
- package/dist/wirings/workflow/scenario-step-guards.d.ts +6 -0
- package/dist/wirings/workflow/scenario-step-guards.js +6 -0
- package/dist/wirings/workflow/workflow-errors.d.ts +4 -0
- package/dist/wirings/workflow/workflow-errors.js +4 -0
- package/package.json +1 -1
- package/src/app-leaf-surface.test.ts +2 -2
- package/src/errors/error-handler.ts +11 -0
- package/src/errors/serialized-error.ts +3 -0
- package/src/function/function-runner.ts +4 -4
- package/src/function/functions.types.ts +33 -21
- package/src/middleware/auth-apikey.ts +7 -0
- package/src/middleware/auth-bearer.ts +3 -0
- package/src/middleware/auth-cookie.ts +9 -4
- package/src/middleware/cors.ts +8 -0
- package/src/middleware/middleware-factories.ts +14 -0
- package/src/public-surface.json +3 -0
- package/src/services/local-secrets.test.ts +2 -2
- package/src/services/local-secrets.ts +1 -1
- package/src/services/personas-service.ts +4 -0
- package/src/services/secret-service.ts +0 -1
- package/src/testing/service-tests/secret-service-tests.ts +1 -1
- package/src/types/core.types.ts +5 -0
- package/src/wirings/addon/addon-auth-tags.test.ts +4 -4
- package/src/wirings/addon/wire-addon.ts +17 -15
- package/src/wirings/addon/wire-remote-addon.ts +4 -0
- package/src/wirings/agent/agent-authorization.test.ts +7 -3
- package/src/wirings/agent/agent-prepare.ts +2 -2
- package/src/wirings/agent/agent.types.ts +31 -27
- package/src/wirings/agent-scorer/agent-scorer.ts +4 -0
- package/src/wirings/channel/channel.types.ts +11 -0
- package/src/wirings/cli/cli.types.ts +44 -1
- package/src/wirings/cli/define-cli-commands.ts +6 -1
- package/src/wirings/credential/credential.types.ts +7 -0
- package/src/wirings/gateway/gateway-authorization.test.ts +3 -3
- package/src/wirings/gateway/gateway.types.ts +23 -0
- package/src/wirings/http/http-runner-addon-ref.test.ts +1 -1
- package/src/wirings/http/http.types.ts +94 -69
- package/src/wirings/mcp/mcp.types.ts +27 -0
- package/src/wirings/persona/index.ts +5 -0
- package/src/wirings/persona/persona-app-scopes.test.ts +47 -0
- package/src/wirings/persona/persona-app-scopes.ts +74 -0
- package/src/wirings/queue/queue.types.ts +6 -0
- package/src/wirings/scheduler/scheduler.types.ts +5 -0
- package/src/wirings/secret/secret.types.ts +9 -14
- package/src/wirings/trigger/trigger.types.ts +4 -1
- package/src/wirings/variable/variable.types.ts +14 -13
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
- package/src/wirings/workflow/scenario-cookie-jar.ts +6 -0
- package/src/wirings/workflow/scenario-poll.ts +6 -0
- package/src/wirings/workflow/scenario-step-guards.ts +6 -0
- package/src/wirings/workflow/workflow-errors.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import type { OAuth2CredentialConfig } from '../secret/secret.types.js'
|
|
2
2
|
|
|
3
3
|
export type CoreCredential<T = unknown> = {
|
|
4
|
+
/** How the credential is asked for in code. Generated into `CredentialsMap`, so it is what `credentials.get` autocompletes. */
|
|
4
5
|
name: string
|
|
6
|
+
/** The name shown to whoever has to supply the value, who is often not the person who wrote this. */
|
|
5
7
|
displayName: string
|
|
8
|
+
/** What the credential is for and where to obtain one. */
|
|
6
9
|
description?: string
|
|
10
|
+
/** `singleton` is one value for the whole deployment; `wire` is one per user, supplied by them and stored against their account. */
|
|
7
11
|
type: 'singleton' | 'wire'
|
|
12
|
+
/** The shape of the value, validated when it is supplied rather than when it is first used. */
|
|
8
13
|
schema: T
|
|
14
|
+
/** Where to go to create one, shown next to the field asking for it. */
|
|
9
15
|
docsUrl?: string
|
|
16
|
+
/** Makes this an OAuth connection rather than a value pasted in: the user is sent to the provider and the tokens are stored for them. */
|
|
10
17
|
oauth2?: OAuth2CredentialConfig & {
|
|
11
18
|
appCredentialSecretId: string
|
|
12
19
|
}
|
|
@@ -280,7 +280,7 @@ describe('gateway handler authorization', () => {
|
|
|
280
280
|
|
|
281
281
|
const response = await postMessage('/webhooks/declared-session')
|
|
282
282
|
|
|
283
|
-
assert.equal(response.status,
|
|
283
|
+
assert.equal(response.status, 401)
|
|
284
284
|
assert.deepEqual(
|
|
285
285
|
calls,
|
|
286
286
|
[],
|
|
@@ -339,7 +339,7 @@ describe('gateway handler authorization', () => {
|
|
|
339
339
|
|
|
340
340
|
const response = await postMessage('/webhooks/gw-auth')
|
|
341
341
|
|
|
342
|
-
assert.equal(response.status,
|
|
342
|
+
assert.equal(response.status, 401)
|
|
343
343
|
assert.deepEqual(calls, [], 'auth: true must require a session')
|
|
344
344
|
})
|
|
345
345
|
|
|
@@ -365,7 +365,7 @@ describe('gateway handler authorization', () => {
|
|
|
365
365
|
|
|
366
366
|
const response = await postMessage('/webhooks/handler-auth')
|
|
367
367
|
|
|
368
|
-
assert.equal(response.status,
|
|
368
|
+
assert.equal(response.status, 401)
|
|
369
369
|
assert.deepEqual(calls, [], 'auth: true must require a session')
|
|
370
370
|
})
|
|
371
371
|
|
|
@@ -17,6 +17,10 @@ export interface GatewayAttachment {
|
|
|
17
17
|
filename?: string
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* One message arriving from a gateway, normalised: who sent it, in which
|
|
22
|
+
* conversation, and what they said.
|
|
23
|
+
*/
|
|
20
24
|
export interface GatewayInboundMessage {
|
|
21
25
|
/** Platform-specific: a phone number, a Slack user id, and so on. */
|
|
22
26
|
senderId: string
|
|
@@ -26,15 +30,27 @@ export interface GatewayInboundMessage {
|
|
|
26
30
|
metadata?: Record<string, unknown>
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
/**
|
|
34
|
+
* One message to send back through a gateway — plain text, or the provider's
|
|
35
|
+
* own rich content.
|
|
36
|
+
*/
|
|
29
37
|
export interface GatewayOutboundMessage {
|
|
30
38
|
text?: string
|
|
31
39
|
richContent?: Record<string, unknown>
|
|
32
40
|
attachments?: GatewayAttachment[]
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
/**
|
|
44
|
+
* What a gateway's `verifyWebhook` returns — verified, with the response the
|
|
45
|
+
* provider expects back, or not.
|
|
46
|
+
*/
|
|
35
47
|
export type WebhookVerificationResult =
|
|
36
48
|
{ verified: true; response: unknown } | { verified: false }
|
|
37
49
|
|
|
50
|
+
/**
|
|
51
|
+
* What a gateway integration implements: parse an incoming event into a
|
|
52
|
+
* message, send one back, and open and close the connection.
|
|
53
|
+
*/
|
|
38
54
|
export interface GatewayAdapter {
|
|
39
55
|
name: string
|
|
40
56
|
/** Return null to ignore the event, e.g. a delivery receipt. */
|
|
@@ -70,14 +86,21 @@ export type CoreGateway<
|
|
|
70
86
|
> = Partial<
|
|
71
87
|
Pick<CommonWireMeta, 'title' | 'summary' | 'description' | 'errors'>
|
|
72
88
|
> & {
|
|
89
|
+
/** Unique across the project. It is how the gateway is addressed in `pikku meta` and in logs. */
|
|
73
90
|
name: string
|
|
91
|
+
/** How the platform reaches us: a `webhook` it posts to, a `websocket` it holds open, or a `listener` we open outward. */
|
|
74
92
|
type: GatewayTransportType
|
|
75
93
|
/** Required for 'webhook' and 'websocket'; unused for 'listener'. */
|
|
76
94
|
route?: string
|
|
95
|
+
/** Which service this speaks to — slack, whatsapp, discord. It selects the adapter's dialect, not the transport. */
|
|
77
96
|
platform?: string
|
|
97
|
+
/** Translates between the platform's message format and pikku's. A factory is called with services, for an adapter that needs a token or a client. */
|
|
78
98
|
adapter: GatewayAdapter | GatewayAdapterFactory
|
|
99
|
+
/** The function to run per inbound message. It receives the normalised message, not the platform's raw payload. */
|
|
79
100
|
func: PikkuFunctionConfig
|
|
101
|
+
/** Wraps every inbound message: signature verification, tracing, rate limiting. */
|
|
80
102
|
middleware?: CorePikkuMiddlewareGroup<any, any>
|
|
103
|
+
/** Filters this gateway in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
81
104
|
tags?: string[]
|
|
82
105
|
/** Unset lets the handler's own `auth` govern; gateway handlers are sessionless by default. */
|
|
83
106
|
auth?: boolean
|
|
@@ -233,6 +233,6 @@ describe('http routes wired with ref() to an addon function', () => {
|
|
|
233
233
|
|
|
234
234
|
const response = await fetch(new ParamsRequest('/addon/greet', 'get'))
|
|
235
235
|
|
|
236
|
-
assert.strictEqual(response.status,
|
|
236
|
+
assert.strictEqual(response.status, 401)
|
|
237
237
|
})
|
|
238
238
|
})
|
|
@@ -48,15 +48,19 @@ export type HTTPHeadersSchema = StandardSchemaV1<
|
|
|
48
48
|
>
|
|
49
49
|
|
|
50
50
|
export type HTTPRouteBaseConfig = {
|
|
51
|
+
/** How the body is serialised. Defaults to JSON; `xml` is for routes a caller you do not control insists on. */
|
|
51
52
|
contentType?: 'xml' | 'json'
|
|
53
|
+
/** Seconds before the request is abandoned. Work that can outlast a request should be dispatched instead, not given a longer timeout. */
|
|
52
54
|
timeout?: number
|
|
55
|
+
/** Filters this route in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
53
56
|
tags?: string[]
|
|
57
|
+
/** A schema the request headers are validated against, so a missing or malformed header fails before the function body runs. */
|
|
54
58
|
headers?: HTTPHeadersSchema
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
export type CoreHTTPFunction = HTTPRouteBaseConfig & {
|
|
58
62
|
route: string
|
|
59
|
-
|
|
63
|
+
/** Sends the returned value as-is rather than JSON-encoding it, for a route whose body is binary or already serialised. */
|
|
60
64
|
returnsJSON?: false
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -70,100 +74,121 @@ export type PikkuQuery<T = Record<string, string | undefined>> = Record<
|
|
|
70
74
|
string | T | null | Array<T | null>
|
|
71
75
|
>
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The keys every HTTP wiring takes, whatever it is wired to. The three that
|
|
79
|
+
* are not here — `method`, `auth` and `func` — are correlated rather than
|
|
80
|
+
* independent, and live in the unions below.
|
|
81
|
+
*/
|
|
82
|
+
type HTTPWiringShared<
|
|
83
|
+
R extends string,
|
|
84
|
+
PikkuMiddleware extends CorePikkuMiddleware<any, any>,
|
|
85
|
+
> = CoreHTTPFunction & {
|
|
86
|
+
/**
|
|
87
|
+
* The path this wiring answers on. `:name` marks a parameter, and every
|
|
88
|
+
* parameter in the path must be a key of the function's input schema —
|
|
89
|
+
* a mismatch is a compile error rather than a 404 at runtime.
|
|
90
|
+
*/
|
|
91
|
+
route: R
|
|
92
|
+
/** Wraps every request to this route: auth, tracing, rate limiting. Runs before the permissions on `func`. */
|
|
93
|
+
middleware?: PikkuMiddleware[]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* `auth` decides which kind of function this route can point at, so the two
|
|
98
|
+
* travel together: a route that requires a session gets a function that is
|
|
99
|
+
* handed one, and an open route gets a sessionless function that has none to
|
|
100
|
+
* read. Splitting them would let a function ask for a session the route never
|
|
101
|
+
* establishes.
|
|
102
|
+
*/
|
|
103
|
+
type HTTPWiringAuth<
|
|
74
104
|
In,
|
|
75
105
|
Out,
|
|
76
|
-
|
|
77
|
-
PikkuFunction extends CorePikkuFunction<In, Out, any, any, any> =
|
|
78
|
-
CorePikkuFunction<In, Out>,
|
|
106
|
+
PikkuFunction extends CorePikkuFunction<In, Out, any, any, any>,
|
|
79
107
|
PikkuFunctionSessionless extends CorePikkuFunctionSessionless<
|
|
80
108
|
In,
|
|
81
109
|
Out,
|
|
82
110
|
any,
|
|
83
111
|
any,
|
|
84
112
|
any
|
|
85
|
-
> = CorePikkuFunctionSessionless<In, Out>,
|
|
86
|
-
PikkuPermission extends CorePikkuPermission<In, any, any> =
|
|
87
|
-
CorePikkuPermission<In, any, any>,
|
|
88
|
-
PikkuMiddleware extends CorePikkuMiddleware<any, any> = CorePikkuMiddleware<
|
|
89
|
-
any,
|
|
90
|
-
any
|
|
91
113
|
>,
|
|
114
|
+
PikkuPermission extends CorePikkuPermission<In, any, any>,
|
|
115
|
+
PikkuMiddleware extends CorePikkuMiddleware<any, any>,
|
|
92
116
|
> =
|
|
93
|
-
|
|
|
94
|
-
route
|
|
95
|
-
|
|
117
|
+
| {
|
|
118
|
+
/** Whether reaching this route requires a session. Defaults to true — a route is closed unless it says otherwise. */
|
|
119
|
+
auth?: true
|
|
120
|
+
/** The function to run. It is handed the session this route required. */
|
|
96
121
|
func: CorePikkuFunctionConfig<
|
|
97
122
|
PikkuFunction,
|
|
98
123
|
PikkuPermission,
|
|
99
124
|
PikkuMiddleware
|
|
100
125
|
>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
route: R
|
|
107
|
-
method: HTTPMethod
|
|
126
|
+
}
|
|
127
|
+
| {
|
|
128
|
+
/** Whether reaching this route requires a session. Defaults to true — a route is closed unless it says otherwise. */
|
|
129
|
+
auth?: false
|
|
130
|
+
/** On an open route there is no session, so this must be a sessionless function. */
|
|
108
131
|
func: CorePikkuFunctionConfig<
|
|
109
132
|
PikkuFunctionSessionless,
|
|
110
133
|
PikkuPermission,
|
|
111
134
|
PikkuMiddleware
|
|
112
135
|
>
|
|
113
|
-
|
|
114
|
-
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* `sse` and `query` are each valid on one method only, so the method carries
|
|
140
|
+
* them: streaming is a GET, and naming which input keys arrive in the query
|
|
141
|
+
* string is only a question on a POST, where the rest of the input is a body.
|
|
142
|
+
*/
|
|
143
|
+
type HTTPWiringMethod<In> =
|
|
144
|
+
| {
|
|
145
|
+
/** The HTTP method. A route and method together address one wiring. */
|
|
146
|
+
method: HTTPMethod
|
|
115
147
|
sse?: undefined
|
|
116
|
-
}
|
|
117
|
-
|
|
|
118
|
-
route
|
|
148
|
+
}
|
|
149
|
+
| {
|
|
150
|
+
/** The HTTP method. A route and method together address one wiring. */
|
|
119
151
|
method: 'get'
|
|
120
|
-
|
|
121
|
-
PikkuFunction,
|
|
122
|
-
PikkuPermission,
|
|
123
|
-
PikkuMiddleware
|
|
124
|
-
>
|
|
125
|
-
auth?: true
|
|
126
|
-
middleware?: PikkuMiddleware[]
|
|
127
|
-
sse?: boolean
|
|
128
|
-
})
|
|
129
|
-
| (CoreHTTPFunction & {
|
|
130
|
-
route: R
|
|
131
|
-
method: 'get'
|
|
132
|
-
func: CorePikkuFunctionConfig<
|
|
133
|
-
PikkuFunctionSessionless,
|
|
134
|
-
PikkuPermission,
|
|
135
|
-
PikkuMiddleware
|
|
136
|
-
>
|
|
137
|
-
auth?: false
|
|
138
|
-
middleware?: PikkuMiddleware[]
|
|
152
|
+
/** Streams the response as server-sent events instead of returning it once. GET only. */
|
|
139
153
|
sse?: boolean
|
|
140
|
-
}
|
|
141
|
-
|
|
|
142
|
-
route
|
|
154
|
+
}
|
|
155
|
+
| {
|
|
156
|
+
/** The HTTP method. A route and method together address one wiring. */
|
|
143
157
|
method: 'post'
|
|
144
|
-
|
|
145
|
-
PikkuFunction,
|
|
146
|
-
PikkuPermission,
|
|
147
|
-
PikkuMiddleware
|
|
148
|
-
>
|
|
149
|
-
auth?: true
|
|
150
|
-
middleware?: PikkuMiddleware[]
|
|
158
|
+
/** Input keys that arrive in the query string rather than the body. POST only, where the body is the default home for input. */
|
|
151
159
|
query?: Array<keyof In>
|
|
152
160
|
sse?: undefined
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export type CoreHTTPFunctionWiring<
|
|
164
|
+
In,
|
|
165
|
+
Out,
|
|
166
|
+
R extends string,
|
|
167
|
+
PikkuFunction extends CorePikkuFunction<In, Out, any, any, any> =
|
|
168
|
+
CorePikkuFunction<In, Out>,
|
|
169
|
+
PikkuFunctionSessionless extends CorePikkuFunctionSessionless<
|
|
170
|
+
In,
|
|
171
|
+
Out,
|
|
172
|
+
any,
|
|
173
|
+
any,
|
|
174
|
+
any
|
|
175
|
+
> = CorePikkuFunctionSessionless<In, Out>,
|
|
176
|
+
PikkuPermission extends CorePikkuPermission<In, any, any> =
|
|
177
|
+
CorePikkuPermission<In, any, any>,
|
|
178
|
+
PikkuMiddleware extends CorePikkuMiddleware<any, any> = CorePikkuMiddleware<
|
|
179
|
+
any,
|
|
180
|
+
any
|
|
181
|
+
>,
|
|
182
|
+
> = HTTPWiringShared<R, PikkuMiddleware> &
|
|
183
|
+
HTTPWiringAuth<
|
|
184
|
+
In,
|
|
185
|
+
Out,
|
|
186
|
+
PikkuFunction,
|
|
187
|
+
PikkuFunctionSessionless,
|
|
188
|
+
PikkuPermission,
|
|
189
|
+
PikkuMiddleware
|
|
190
|
+
> &
|
|
191
|
+
HTTPWiringMethod<In>
|
|
167
192
|
|
|
168
193
|
export type HTTPFunctionMetaInputTypes = {
|
|
169
194
|
params?: string
|
|
@@ -76,16 +76,27 @@ export type CoreMCPResource<
|
|
|
76
76
|
PikkuPermission = CorePikkuPermission<any, any>,
|
|
77
77
|
PikkuMiddleware = CorePikkuMiddleware<any>,
|
|
78
78
|
> = {
|
|
79
|
+
/** How the client addresses this resource. `{name}` marks a parameter, and every parameter must be a key of the function's input schema. */
|
|
79
80
|
uri: string
|
|
81
|
+
/** The name a human sees in a client's resource list. */
|
|
80
82
|
title: string
|
|
83
|
+
/** What the resource holds, written for the model deciding whether to read it. */
|
|
81
84
|
description: string
|
|
85
|
+
/** A one-line description for listings, where the full `description` is too long. */
|
|
82
86
|
summary?: string
|
|
87
|
+
/** Names of error classes this may throw, so the client is told which failures are its own fault. */
|
|
83
88
|
errors?: string[]
|
|
89
|
+
/** The media type of what the function returns, so the client knows whether it is text, JSON or an image. */
|
|
84
90
|
mimeType?: string
|
|
91
|
+
/** Size in bytes, where it is known ahead of the read. A client uses it to decide whether to fetch at all. */
|
|
85
92
|
size?: number
|
|
93
|
+
/** Whether the function returns the content in chunks rather than at once. */
|
|
86
94
|
streaming?: boolean
|
|
95
|
+
/** The function to run. It is sessionless: an MCP client is not a logged-in user. */
|
|
87
96
|
func: PikkuFunctionConfig
|
|
97
|
+
/** Filters this wiring in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
88
98
|
tags?: string[]
|
|
99
|
+
/** Wraps every call: tracing, rate limiting, whatever the transport does not do. */
|
|
89
100
|
middleware?: PikkuMiddleware[]
|
|
90
101
|
}
|
|
91
102
|
|
|
@@ -96,14 +107,23 @@ export type CoreMCPTool<
|
|
|
96
107
|
PikkuPermission = CorePikkuPermission<any, any>,
|
|
97
108
|
PikkuMiddleware = CorePikkuMiddleware<any>,
|
|
98
109
|
> = {
|
|
110
|
+
/** How the model calls this tool. It is the tool's identity, so renaming it breaks any client that already knows it. */
|
|
99
111
|
name: string
|
|
112
|
+
/** The name a human sees, where the calling `name` is not readable. */
|
|
100
113
|
title?: string
|
|
114
|
+
/** What the tool does and when to reach for it. This is what the model decides on, so it earns more care than the rest of this object. */
|
|
101
115
|
description?: string
|
|
116
|
+
/** A one-line description for listings, where the full `description` is too long. */
|
|
102
117
|
summary?: string
|
|
118
|
+
/** Names of error classes this may throw, so the client is told which failures are its own fault. */
|
|
103
119
|
errors?: string[]
|
|
120
|
+
/** The function to run. It is sessionless: an MCP client is not a logged-in user. */
|
|
104
121
|
func: PikkuFunctionConfig
|
|
122
|
+
/** Filters this wiring in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
105
123
|
tags?: string[]
|
|
124
|
+
/** Whether the function returns its result in chunks rather than at once. */
|
|
106
125
|
streaming?: boolean
|
|
126
|
+
/** Wraps every call: tracing, rate limiting, whatever the transport does not do. */
|
|
107
127
|
middleware?: PikkuMiddleware[]
|
|
108
128
|
}
|
|
109
129
|
|
|
@@ -114,12 +134,19 @@ export type CoreMCPPrompt<
|
|
|
114
134
|
PikkuPermission = CorePikkuPermission<any, any>,
|
|
115
135
|
PikkuMiddleware = CorePikkuMiddleware<any>,
|
|
116
136
|
> = {
|
|
137
|
+
/** How the client asks for this prompt. */
|
|
117
138
|
name: string
|
|
139
|
+
/** What the prompt is for, written for the human picking it out of a list. */
|
|
118
140
|
description: string
|
|
141
|
+
/** A one-line description for listings, where the full `description` is too long. */
|
|
119
142
|
summary?: string
|
|
143
|
+
/** Names of error classes this may throw, so the client is told which failures are its own fault. */
|
|
120
144
|
errors?: string[]
|
|
145
|
+
/** The function to run. It is sessionless: an MCP client is not a logged-in user. */
|
|
121
146
|
func: PikkuFunctionConfig
|
|
147
|
+
/** Filters this wiring in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
122
148
|
tags?: string[]
|
|
149
|
+
/** Wraps every call: tracing, rate limiting, whatever the transport does not do. */
|
|
123
150
|
middleware?: PikkuMiddleware[]
|
|
124
151
|
}
|
|
125
152
|
|
|
@@ -16,6 +16,11 @@ export type {
|
|
|
16
16
|
PersonaEnvironmentSubject,
|
|
17
17
|
} from './persona-environments.js'
|
|
18
18
|
export { personaEmail, personaEmails } from './persona-email.js'
|
|
19
|
+
export {
|
|
20
|
+
APP_SCOPE_ROOT,
|
|
21
|
+
appScopeId,
|
|
22
|
+
buildAppScopeDefinition,
|
|
23
|
+
} from './persona-app-scopes.js'
|
|
19
24
|
export type {
|
|
20
25
|
MailboxAllowlist,
|
|
21
26
|
PersonaMailbox,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, test } from 'node:test'
|
|
3
|
+
import {
|
|
4
|
+
appScopeId,
|
|
5
|
+
buildAppScopeDefinition,
|
|
6
|
+
declaredApps,
|
|
7
|
+
} from './persona-app-scopes.js'
|
|
8
|
+
|
|
9
|
+
const persona = (id: string, app?: string) =>
|
|
10
|
+
({ id, name: id, roles: [], goals: [], tags: [], runnable: true, app }) as any
|
|
11
|
+
|
|
12
|
+
describe('declaredApps', () => {
|
|
13
|
+
test('deduplicates and sorts the apps the personas name', () => {
|
|
14
|
+
assert.deepEqual(
|
|
15
|
+
declaredApps([
|
|
16
|
+
persona('a', 'staff'),
|
|
17
|
+
persona('b', 'portal'),
|
|
18
|
+
persona('c', 'staff'),
|
|
19
|
+
]),
|
|
20
|
+
['portal', 'staff']
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('ignores personas that name no app', () => {
|
|
25
|
+
assert.deepEqual(declaredApps([persona('a'), persona('b', '')]), [])
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('buildAppScopeDefinition', () => {
|
|
30
|
+
test('renders one grantable child per app under the app root', () => {
|
|
31
|
+
const definition = buildAppScopeDefinition([
|
|
32
|
+
persona('a', 'staff'),
|
|
33
|
+
persona('b', 'portal'),
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
assert.equal(definition?.name, 'app')
|
|
37
|
+
assert.deepEqual(Object.keys(definition?.scopes ?? {}).sort(), [
|
|
38
|
+
'portal',
|
|
39
|
+
'staff',
|
|
40
|
+
])
|
|
41
|
+
assert.equal(appScopeId('staff'), 'app:staff')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('is null for a single-frontend product', () => {
|
|
45
|
+
assert.equal(buildAppScopeDefinition([persona('a')]), null)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ScopeDefinitionMeta, ScopeNodeMeta } from '../scope/scope.types.js'
|
|
2
|
+
import type { PersonaMeta } from './persona.types.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The root every app grant hangs off. One segment, so a grant of `app` alone
|
|
6
|
+
* means "may use all of them" under the same parent-grant rule the admin tree
|
|
7
|
+
* relies on — which is what a support operator or an internal tool wants, and
|
|
8
|
+
* what a per-app boolean column could never express.
|
|
9
|
+
*/
|
|
10
|
+
export const APP_SCOPE_ROOT = 'app'
|
|
11
|
+
|
|
12
|
+
/** `app:staff` for `staff`. */
|
|
13
|
+
export const appScopeId = (app: string) => `${APP_SCOPE_ROOT}:${app}`
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The apps named across the declared personas, sorted and deduplicated.
|
|
17
|
+
*
|
|
18
|
+
* The personas *are* the registry. There is no separate list of apps to keep in
|
|
19
|
+
* step, because a frontend nobody signs into is not a thing the auth layer has
|
|
20
|
+
* an opinion about — and the moment somebody does sign into it, they are a
|
|
21
|
+
* persona and they name it.
|
|
22
|
+
*/
|
|
23
|
+
export const declaredApps = (personas: PersonaMeta[]): string[] =>
|
|
24
|
+
[
|
|
25
|
+
...new Set(
|
|
26
|
+
personas
|
|
27
|
+
.map((persona) => persona.app)
|
|
28
|
+
.filter((app): app is string => typeof app === 'string' && app !== '')
|
|
29
|
+
),
|
|
30
|
+
].sort()
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The `app` scope tree, synthesised from the personas that name an app.
|
|
34
|
+
*
|
|
35
|
+
* Which frontend a person may sign into is a different question from what they
|
|
36
|
+
* may do once inside it, but it is the same *kind* of question — a grant that
|
|
37
|
+
* an admin can make and revoke at runtime, that resolves at the session
|
|
38
|
+
* boundary, and that a restricted API key can decline to inherit. So it is
|
|
39
|
+
* carried as a scope rather than a `can_access_<app>` column: no migration per
|
|
40
|
+
* app, one query for "which apps may this user reach", and no second
|
|
41
|
+
* authorization mechanism to keep honest.
|
|
42
|
+
*
|
|
43
|
+
* Synthesised rather than written by hand because the declaration already
|
|
44
|
+
* exists. Asking an app to also spell out `defineScope({ app: { staff: {} } })`
|
|
45
|
+
* beside its personas invites the two to drift, and the failure that produces —
|
|
46
|
+
* a persona provisioned with a grant the vocabulary no longer declares — is one
|
|
47
|
+
* `pikku scopes prune` away from silently revoking sign-in.
|
|
48
|
+
*
|
|
49
|
+
* Null when no persona names an app, which is the single-frontend case: nothing
|
|
50
|
+
* to declare, and no empty root cluttering the console's grant list.
|
|
51
|
+
*/
|
|
52
|
+
export const buildAppScopeDefinition = (
|
|
53
|
+
personas: PersonaMeta[]
|
|
54
|
+
): ScopeDefinitionMeta | null => {
|
|
55
|
+
const apps = declaredApps(personas)
|
|
56
|
+
if (apps.length === 0) {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const scopes: Record<string, ScopeNodeMeta> = {}
|
|
61
|
+
for (const app of apps) {
|
|
62
|
+
scopes[app] = {
|
|
63
|
+
displayName: app,
|
|
64
|
+
description: `May sign in to the ${app} app`,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
name: APP_SCOPE_ROOT,
|
|
70
|
+
displayName: 'Apps',
|
|
71
|
+
description: 'Which frontend a person may sign in to',
|
|
72
|
+
scopes,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -135,11 +135,17 @@ export type CoreQueueWorker<
|
|
|
135
135
|
PikkuFunctionConfig extends CorePikkuFunctionConfig<any, any, any> =
|
|
136
136
|
CorePikkuFunctionConfig<any, any, any>,
|
|
137
137
|
> = {
|
|
138
|
+
/** The queue this worker consumes. Whoever enqueues a job names the same string, so it is the contract between producer and consumer. */
|
|
138
139
|
name: string
|
|
140
|
+
/** The function to run per job. Its `input` schema is the job payload's schema — a job that does not match is rejected before the body runs. */
|
|
139
141
|
func: PikkuFunctionConfig
|
|
142
|
+
/** Concurrency, retry and backoff, passed through to the queue service backing this worker. Defaults come from the service, not from here. */
|
|
140
143
|
config?: PikkuWorkerConfig
|
|
144
|
+
/** Names of error classes that mean the job is bad rather than the run — thrown, they fail the job permanently instead of being retried. */
|
|
141
145
|
errors?: string[]
|
|
146
|
+
/** Filters this worker in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
142
147
|
tags?: string[]
|
|
148
|
+
/** Wraps every job. There is no request to read from, so this is for tracing, locking and teardown rather than auth. */
|
|
143
149
|
middleware?: PikkuFunctionConfig['middleware']
|
|
144
150
|
}
|
|
145
151
|
|
|
@@ -21,10 +21,15 @@ export type CoreScheduledTask<
|
|
|
21
21
|
>,
|
|
22
22
|
PikkuMiddleware = CorePikkuMiddleware<any>,
|
|
23
23
|
> = {
|
|
24
|
+
/** Unique across the project. It is how the task is addressed in logs, in `pikku meta`, and by a scheduler service asked to run it now. */
|
|
24
25
|
name: string
|
|
26
|
+
/** A five-field cron expression: minute, hour, day of month, month, day of week. `0 9 * * 1` is 09:00 every Monday. Interpreted in the deployment's timezone, not the author's. */
|
|
25
27
|
schedule: string
|
|
28
|
+
/** The function to run. It receives no session and no input: a scheduled task has no caller, so it must be sessionless. */
|
|
26
29
|
func: PikkuFunctionConfig
|
|
30
|
+
/** Filters this task in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
27
31
|
tags?: string[]
|
|
32
|
+
/** Wraps every execution. There is no request to read from, so this is for tracing, locking and teardown rather than auth. */
|
|
28
33
|
middleware?: PikkuMiddleware[]
|
|
29
34
|
}
|
|
30
35
|
|
|
@@ -4,21 +4,9 @@ export type CoreSecret<T = unknown> = {
|
|
|
4
4
|
description?: string
|
|
5
5
|
secretId: string
|
|
6
6
|
schema: T
|
|
7
|
-
/**
|
|
8
|
-
* A secret is REQUIRED by default because `getSecret` throws when the value is
|
|
9
|
-
* missing — a deploy without it is a runtime failure waiting to happen, so the
|
|
10
|
-
* deploy gate refuses it. Mark a secret `optional` when absence is a supported
|
|
11
|
-
* state the app handles (a feature that simply stays off): the generated map
|
|
12
|
-
* emits it as an optional property and `getSecret` resolves `undefined`
|
|
13
|
-
* instead of throwing.
|
|
14
|
-
*/
|
|
7
|
+
/** Required by default: this says absence is a supported state, and `getSecret` resolves `undefined` rather than throwing. */
|
|
15
8
|
optional?: boolean
|
|
16
|
-
/**
|
|
17
|
-
* Where a user goes to obtain this value — a provider's API-key page, a setup
|
|
18
|
-
* guide, an internal runbook. Consoles and deploy UIs surface it beside a
|
|
19
|
-
* missing value. The same field on every credential/variable definition means
|
|
20
|
-
* the same thing.
|
|
21
|
-
*/
|
|
9
|
+
/** Where a user goes to obtain this value, surfaced beside a missing one. */
|
|
22
10
|
docsUrl?: string
|
|
23
11
|
/** Rotation cadence as a duration string, e.g. `'1d'`, `'30day'`, `'1w'`. */
|
|
24
12
|
rotationPeriod?: string
|
|
@@ -64,4 +52,11 @@ export type SecretDefinitionsMeta = Record<string, SecretDefinitionMeta>
|
|
|
64
52
|
|
|
65
53
|
export type SecretDefinitions = SecretDefinitionMeta[]
|
|
66
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Declares a secret this project needs, with the shape of its value. The CLI
|
|
57
|
+
* collects every declaration into `CredentialsMap`, which is what makes
|
|
58
|
+
* `secrets.getSecret('NAME')` return the right type instead of `unknown`.
|
|
59
|
+
*
|
|
60
|
+
* @example snippet: secrets
|
|
61
|
+
*/
|
|
67
62
|
export const defineSecret = <T>(_config: CoreSecret<T>): void => {}
|
|
@@ -72,11 +72,14 @@ export const pikkuTriggerFunc = <
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export interface CoreTrigger<PikkuFunctionConfig = any> {
|
|
75
|
+
/** What a `wireTriggerSource` points at to fire this trigger. It is the contract between the two, so both must spell it the same. */
|
|
75
76
|
name: string
|
|
77
|
+
/** The function to run each time the trigger fires. */
|
|
76
78
|
func: PikkuFunctionConfig
|
|
79
|
+
/** What firing this trigger means, for whoever is reading the wiring rather than writing it. */
|
|
77
80
|
description?: string
|
|
81
|
+
/** Filters this trigger in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
78
82
|
tags?: string[]
|
|
79
|
-
graph?: true
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
export interface TriggerInstance {
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
export type CoreVariable<T = unknown> = {
|
|
2
|
+
/** How the variable is asked for in code. Generated into `VariablesMap`, so it is what `variables.get` autocompletes. */
|
|
2
3
|
name: string
|
|
4
|
+
/** The name shown to whoever configures the deployment. */
|
|
3
5
|
displayName: string
|
|
6
|
+
/** What the value does, for the person setting it rather than the one reading it. */
|
|
4
7
|
description?: string
|
|
8
|
+
/** The environment variable this reads, which is the name that has to exist on the host. */
|
|
5
9
|
variableId: string
|
|
10
|
+
/** The shape of the value. It arrives as a string, so this is also what parses it. */
|
|
6
11
|
schema: T
|
|
7
|
-
/**
|
|
8
|
-
* A variable is REQUIRED by default, and marking it `optional` is how a
|
|
9
|
-
* declaration says its absence is a supported state. Same flag, same
|
|
10
|
-
* polarity and same meaning as `CoreSecret.optional` — one word to learn
|
|
11
|
-
* rather than two with opposite senses.
|
|
12
|
-
*
|
|
13
|
-
* Defaulting to required rather than following `variables.get`'s
|
|
14
|
-
* `T | undefined` return is deliberate. That signature describes what a
|
|
15
|
-
* caller must HANDLE, not whether a deployment is correct without the value:
|
|
16
|
-
* an undefined feature flag is fine, an undefined API base URL is an outage
|
|
17
|
-
* that the type system cannot tell apart. Declaring the difference is the
|
|
18
|
-
* point of the flag, and the safe default for an undeclared one is to ask.
|
|
19
|
-
*/
|
|
12
|
+
/** Required by default: this says the deployment is still correct without it. */
|
|
20
13
|
optional?: boolean
|
|
14
|
+
/** Where to go to work out what to set this to. */
|
|
21
15
|
docsUrl?: string
|
|
22
16
|
}
|
|
23
17
|
|
|
@@ -36,4 +30,11 @@ export type VariableDefinitionsMeta = Record<string, VariableDefinitionMeta>
|
|
|
36
30
|
|
|
37
31
|
export type VariableDefinitions = VariableDefinitionMeta[]
|
|
38
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Declares an environment variable this project needs, with the shape of its
|
|
35
|
+
* value. The CLI collects every declaration into `VariablesMap`, which is what
|
|
36
|
+
* makes `variables.get('NAME')` return the right type instead of `unknown`.
|
|
37
|
+
*
|
|
38
|
+
* @example snippet: variables
|
|
39
|
+
*/
|
|
39
40
|
export const defineVariable = <T>(_config: CoreVariable<T>): void => {}
|