@pikku/core 0.12.23 → 0.12.25
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 +30 -0
- package/dist/data-classification.d.ts +16 -0
- package/dist/data-classification.js +1 -0
- package/dist/function/function-runner.js +62 -15
- package/dist/function/functions.types.d.ts +8 -5
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -0
- package/dist/middleware-runner.d.ts +2 -2
- package/dist/permissions.d.ts +1 -1
- package/dist/services/ai-agent-runner-service.d.ts +3 -0
- package/dist/services/audit-service.d.ts +50 -0
- package/dist/services/audit-service.js +106 -0
- package/dist/services/content-service.d.ts +1 -0
- package/dist/services/email-service.d.ts +36 -0
- package/dist/services/email-service.js +1 -0
- package/dist/services/gopass-secrets.d.ts +2 -35
- package/dist/services/gopass-secrets.js +10 -40
- package/dist/services/index.d.ts +5 -1
- package/dist/services/index.js +2 -0
- package/dist/services/local-content.js +1 -0
- package/dist/services/local-email-service.d.ts +4 -0
- package/dist/services/local-email-service.js +26 -0
- package/dist/services/local-secrets.d.ts +3 -36
- package/dist/services/local-secrets.js +12 -52
- package/dist/services/local-variables.d.ts +3 -5
- package/dist/services/local-variables.js +10 -10
- package/dist/services/meta-service.d.ts +36 -0
- package/dist/services/meta-service.js +59 -0
- package/dist/services/scoped-secret-service.d.ts +2 -3
- package/dist/services/scoped-secret-service.js +2 -6
- package/dist/services/secret-service.d.ts +5 -12
- package/dist/services/typed-secret-service.d.ts +3 -4
- package/dist/services/typed-secret-service.js +2 -5
- package/dist/services/typed-variables-service.d.ts +3 -6
- package/dist/services/typed-variables-service.js +0 -6
- package/dist/services/variables-service.d.ts +2 -4
- package/dist/testing/service-tests.js +13 -13
- package/dist/types/core.types.d.ts +15 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +2 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.js +12 -3
- package/dist/wirings/ai-agent/ai-agent-runner.js +1 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +1 -1
- package/dist/wirings/ai-agent/ai-agent-utils.d.ts +1 -0
- package/dist/wirings/ai-agent/ai-agent-utils.js +1 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +4 -0
- package/dist/wirings/oauth2/oauth2-client.js +3 -3
- package/dist/wirings/rpc/rpc-runner.js +9 -2
- package/package.json +1 -1
- package/src/data-classification.ts +15 -0
- package/src/function/function-runner.test.ts +255 -0
- package/src/function/function-runner.ts +66 -20
- package/src/function/functions.types.ts +26 -11
- package/src/index.ts +44 -0
- package/src/middleware-runner.ts +10 -10
- package/src/permissions.ts +4 -4
- package/src/services/ai-agent-runner-service.ts +3 -0
- package/src/services/audit-service.test.ts +44 -0
- package/src/services/audit-service.ts +198 -0
- package/src/services/content-service.ts +1 -0
- package/src/services/email-service.ts +46 -0
- package/src/services/gopass-secrets.ts +10 -42
- package/src/services/index.ts +33 -0
- package/src/services/local-content.ts +1 -0
- package/src/services/local-email-service.test.ts +108 -0
- package/src/services/local-email-service.ts +30 -0
- package/src/services/local-secrets.test.ts +10 -10
- package/src/services/local-secrets.ts +16 -55
- package/src/services/local-variables.test.ts +4 -4
- package/src/services/local-variables.ts +12 -17
- package/src/services/meta-service.ts +115 -0
- package/src/services/scoped-secret-service.test.ts +10 -11
- package/src/services/scoped-secret-service.ts +4 -9
- package/src/services/secret-service.ts +5 -12
- package/src/services/typed-secret-service.test.ts +16 -17
- package/src/services/typed-secret-service.ts +5 -9
- package/src/services/typed-variables-service.test.ts +5 -5
- package/src/services/typed-variables-service.ts +5 -17
- package/src/services/variables-service.ts +2 -4
- package/src/testing/service-tests.ts +13 -13
- package/src/types/core.types.ts +20 -2
- package/src/wirings/ai-agent/ai-agent-prepare.ts +17 -3
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +105 -0
- package/src/wirings/ai-agent/ai-agent-runner.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-stream.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-utils.ts +1 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +4 -0
- package/src/wirings/oauth2/oauth2-client.test.ts +2 -3
- package/src/wirings/oauth2/oauth2-client.ts +3 -3
- package/src/wirings/rpc/rpc-runner.ts +9 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
createWireServicesCredentialWireProps,
|
|
37
37
|
} from '../services/credential-wire-service.js'
|
|
38
38
|
import { defaultPikkuUserIdResolver } from '../services/pikku-user-id.js'
|
|
39
|
+
import { resolveAuditConfig } from '../services/audit-service.js'
|
|
39
40
|
import { rpcService } from '../wirings/rpc/rpc-runner.js'
|
|
40
41
|
import { closeWireServices } from '../utils.js'
|
|
41
42
|
|
|
@@ -288,6 +289,8 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
288
289
|
throw new Error(`Function meta not found: ${funcName}`)
|
|
289
290
|
}
|
|
290
291
|
|
|
292
|
+
const resolvedFunctionId = funcMeta.pikkuFuncId ?? funcName
|
|
293
|
+
|
|
291
294
|
// For addon packages, get or create their singleton services
|
|
292
295
|
const resolvedSingletonServices = packageName
|
|
293
296
|
? await getOrCreatePackageSingletonServices(packageName, singletonServices)
|
|
@@ -332,6 +335,17 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
332
335
|
)
|
|
333
336
|
}
|
|
334
337
|
|
|
338
|
+
const resolvedAuditConfig = resolveAuditConfig(funcConfig.audit)
|
|
339
|
+
const invocationWire = resolvedWire as PikkuWire
|
|
340
|
+
const previousFunctionId = invocationWire.functionId
|
|
341
|
+
const previousAudit = invocationWire.audit
|
|
342
|
+
const previousRpcDescriptor = Object.getOwnPropertyDescriptor(
|
|
343
|
+
invocationWire,
|
|
344
|
+
'rpc'
|
|
345
|
+
)
|
|
346
|
+
invocationWire.functionId = resolvedFunctionId
|
|
347
|
+
invocationWire.audit = resolvedAuditConfig
|
|
348
|
+
|
|
335
349
|
// Convert tags to PermissionMetadata and merge with inheritedPermissions
|
|
336
350
|
let mergedInheritedPermissions: PermissionMetadata[]
|
|
337
351
|
if (tags && tags.length > 0) {
|
|
@@ -346,20 +360,20 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
346
360
|
// Helper function to run permissions and execute the function
|
|
347
361
|
const executeFunction = async () => {
|
|
348
362
|
await resolveSession(
|
|
349
|
-
|
|
363
|
+
invocationWire,
|
|
350
364
|
resolvedSingletonServices,
|
|
351
365
|
sessionService
|
|
352
366
|
)
|
|
353
367
|
|
|
354
368
|
if (sessionService) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
369
|
+
invocationWire.session = sessionService.freezeInitial()
|
|
370
|
+
invocationWire.setSession = (s: any) => sessionService.set(s)
|
|
371
|
+
invocationWire.clearSession = () => sessionService.clear()
|
|
372
|
+
invocationWire.getSession = () => sessionService.get()
|
|
373
|
+
invocationWire.hasSessionChanged = () => sessionService.sessionChanged
|
|
360
374
|
}
|
|
361
375
|
|
|
362
|
-
const session =
|
|
376
|
+
const session = invocationWire.session
|
|
363
377
|
|
|
364
378
|
if (funcMeta.sessionless) {
|
|
365
379
|
if (wiringAuth === true || funcConfig.auth === true) {
|
|
@@ -422,7 +436,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
422
436
|
funcInheritedPermissions: funcMeta.permissions,
|
|
423
437
|
funcPermissions: funcConfig.permissions,
|
|
424
438
|
services: resolvedSingletonServices,
|
|
425
|
-
wire:
|
|
439
|
+
wire: invocationWire as any,
|
|
426
440
|
data: actualData,
|
|
427
441
|
packageName,
|
|
428
442
|
})
|
|
@@ -432,23 +446,23 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
432
446
|
try {
|
|
433
447
|
wireServices = (await resolvedCreateWireServices?.(
|
|
434
448
|
resolvedSingletonServices,
|
|
435
|
-
|
|
449
|
+
invocationWire
|
|
436
450
|
)) as Record<string, unknown> | undefined
|
|
437
451
|
const services =
|
|
438
452
|
wireServices && Object.keys(wireServices).length > 0
|
|
439
453
|
? { ...resolvedSingletonServices, ...wireServices }
|
|
440
454
|
: resolvedSingletonServices
|
|
441
455
|
const callerPackageName = packageName
|
|
442
|
-
Object.defineProperty(
|
|
456
|
+
Object.defineProperty(invocationWire, 'rpc', {
|
|
443
457
|
get() {
|
|
444
458
|
const rpc = rpcService.getContextRPCService(
|
|
445
459
|
services,
|
|
446
|
-
|
|
460
|
+
invocationWire,
|
|
447
461
|
{ sessionService },
|
|
448
462
|
0,
|
|
449
463
|
callerPackageName
|
|
450
464
|
)
|
|
451
|
-
Object.defineProperty(
|
|
465
|
+
Object.defineProperty(invocationWire, 'rpc', {
|
|
452
466
|
value: rpc,
|
|
453
467
|
writable: true,
|
|
454
468
|
configurable: true,
|
|
@@ -458,7 +472,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
458
472
|
configurable: true,
|
|
459
473
|
enumerable: true,
|
|
460
474
|
})
|
|
461
|
-
return await funcConfig.func(services, actualData,
|
|
475
|
+
return await funcConfig.func(services, actualData, invocationWire)
|
|
462
476
|
} finally {
|
|
463
477
|
if (wireServices && Object.keys(wireServices).length > 0) {
|
|
464
478
|
await closeWireServices(resolvedSingletonServices.logger, wireServices)
|
|
@@ -475,13 +489,45 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
475
489
|
})
|
|
476
490
|
|
|
477
491
|
if (allMiddleware.length > 0) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
492
|
+
try {
|
|
493
|
+
return (await runMiddleware<CorePikkuMiddleware>(
|
|
494
|
+
resolvedSingletonServices,
|
|
495
|
+
invocationWire,
|
|
496
|
+
allMiddleware,
|
|
497
|
+
executeFunction
|
|
498
|
+
)) as Out
|
|
499
|
+
} finally {
|
|
500
|
+
if (previousRpcDescriptor) {
|
|
501
|
+
Object.defineProperty(invocationWire, 'rpc', previousRpcDescriptor)
|
|
502
|
+
}
|
|
503
|
+
if (previousFunctionId === undefined) {
|
|
504
|
+
delete (invocationWire as any).functionId
|
|
505
|
+
} else {
|
|
506
|
+
invocationWire.functionId = previousFunctionId
|
|
507
|
+
}
|
|
508
|
+
if (previousAudit === undefined) {
|
|
509
|
+
delete (invocationWire as any).audit
|
|
510
|
+
} else {
|
|
511
|
+
invocationWire.audit = previousAudit
|
|
512
|
+
}
|
|
513
|
+
}
|
|
484
514
|
}
|
|
485
515
|
|
|
486
|
-
|
|
516
|
+
try {
|
|
517
|
+
return (await executeFunction()) as Out
|
|
518
|
+
} finally {
|
|
519
|
+
if (previousRpcDescriptor) {
|
|
520
|
+
Object.defineProperty(invocationWire, 'rpc', previousRpcDescriptor)
|
|
521
|
+
}
|
|
522
|
+
if (previousFunctionId === undefined) {
|
|
523
|
+
delete (invocationWire as any).functionId
|
|
524
|
+
} else {
|
|
525
|
+
invocationWire.functionId = previousFunctionId
|
|
526
|
+
}
|
|
527
|
+
if (previousAudit === undefined) {
|
|
528
|
+
delete (invocationWire as any).audit
|
|
529
|
+
} else {
|
|
530
|
+
invocationWire.audit = previousAudit
|
|
531
|
+
}
|
|
532
|
+
}
|
|
487
533
|
}
|
|
@@ -29,7 +29,12 @@ export type CorePikkuFunction<
|
|
|
29
29
|
Out,
|
|
30
30
|
Services extends CoreSingletonServices = CoreServices,
|
|
31
31
|
Session extends CoreUserSession = CoreUserSession,
|
|
32
|
-
Wire extends PikkuWire<In, Out> = PikkuWire<
|
|
32
|
+
Wire extends PikkuWire<In, Out, true, Session> = PikkuWire<
|
|
33
|
+
In,
|
|
34
|
+
Out,
|
|
35
|
+
true,
|
|
36
|
+
Session
|
|
37
|
+
>,
|
|
33
38
|
> = (
|
|
34
39
|
services: Services,
|
|
35
40
|
data: In,
|
|
@@ -74,8 +79,15 @@ export type CorePikkuFunctionSessionless<
|
|
|
74
79
|
export type CorePikkuPermission<
|
|
75
80
|
In = any,
|
|
76
81
|
Services extends CoreSingletonServices = CoreServices,
|
|
77
|
-
Wire extends PikkuWire<In, never, false,
|
|
78
|
-
|
|
82
|
+
Wire extends PikkuWire<In, never, false, any, any, never, never> = PikkuWire<
|
|
83
|
+
In,
|
|
84
|
+
never,
|
|
85
|
+
false,
|
|
86
|
+
any,
|
|
87
|
+
never,
|
|
88
|
+
never,
|
|
89
|
+
never
|
|
90
|
+
>,
|
|
79
91
|
> = (services: Services, data: In, wire: Wire) => Promise<boolean>
|
|
80
92
|
|
|
81
93
|
/**
|
|
@@ -88,11 +100,11 @@ export type CorePikkuPermission<
|
|
|
88
100
|
export type CorePikkuPermissionConfig<
|
|
89
101
|
In = any,
|
|
90
102
|
Services extends CoreSingletonServices = CoreServices,
|
|
91
|
-
Wire extends PikkuWire<In, never, false,
|
|
103
|
+
Wire extends PikkuWire<In, never, false, any> = PikkuWire<
|
|
92
104
|
In,
|
|
93
105
|
never,
|
|
94
106
|
false,
|
|
95
|
-
|
|
107
|
+
any
|
|
96
108
|
>,
|
|
97
109
|
> = {
|
|
98
110
|
/** The permission function */
|
|
@@ -131,10 +143,8 @@ export type CorePikkuPermissionConfig<
|
|
|
131
143
|
export const pikkuPermission = <
|
|
132
144
|
In = any,
|
|
133
145
|
Services extends CoreSingletonServices = CoreServices,
|
|
134
|
-
Wire extends PickRequired<
|
|
135
|
-
PikkuWire<In, never, false,
|
|
136
|
-
'session'
|
|
137
|
-
> = PickRequired<PikkuWire<In, never, false, CoreUserSession>, 'session'>,
|
|
146
|
+
Wire extends PickRequired<PikkuWire<In, never, false, any>, 'session'> =
|
|
147
|
+
PickRequired<PikkuWire<In, never, false, any>, 'session'>,
|
|
138
148
|
>(
|
|
139
149
|
permission:
|
|
140
150
|
| CorePikkuPermission<In, Services, Wire>
|
|
@@ -154,11 +164,11 @@ export const pikkuPermission = <
|
|
|
154
164
|
export type CorePikkuPermissionFactory<
|
|
155
165
|
In = any,
|
|
156
166
|
Services extends CoreSingletonServices = CoreServices,
|
|
157
|
-
Wire extends PikkuWire<In, never, false,
|
|
167
|
+
Wire extends PikkuWire<In, never, false, any> = PikkuWire<
|
|
158
168
|
In,
|
|
159
169
|
never,
|
|
160
170
|
false,
|
|
161
|
-
|
|
171
|
+
any
|
|
162
172
|
>,
|
|
163
173
|
> = (input: In) => CorePikkuPermission<any, Services, Wire>
|
|
164
174
|
|
|
@@ -285,6 +295,11 @@ export type CorePikkuFunctionConfig<
|
|
|
285
295
|
readonly?: boolean
|
|
286
296
|
deploy?: 'serverless' | 'server' | 'auto'
|
|
287
297
|
approvalRequired?: boolean
|
|
298
|
+
audit?:
|
|
299
|
+
| boolean
|
|
300
|
+
| {
|
|
301
|
+
durability?: 'best-effort' | 'transactional'
|
|
302
|
+
}
|
|
288
303
|
approvalDescription?: any
|
|
289
304
|
func: PikkuFunction
|
|
290
305
|
auth?: boolean
|
package/src/index.ts
CHANGED
|
@@ -96,6 +96,15 @@ export { NotFoundError } from './errors/errors.js'
|
|
|
96
96
|
export type { EventHubService } from './wirings/channel/eventhub-service.js'
|
|
97
97
|
export type { QueueService } from './wirings/queue/queue.types.js'
|
|
98
98
|
export type { JWTService } from './services/jwt-service.js'
|
|
99
|
+
export type {
|
|
100
|
+
EmailService,
|
|
101
|
+
EmailTemplateReference,
|
|
102
|
+
SendEmailInput,
|
|
103
|
+
SendEmailResult,
|
|
104
|
+
SendHTMLEmailInput,
|
|
105
|
+
SendTemplateEmailInput,
|
|
106
|
+
SendTextEmailInput,
|
|
107
|
+
} from './services/email-service.js'
|
|
99
108
|
export type { SecretService } from './services/secret-service.js'
|
|
100
109
|
export type { VariablesService } from './services/variables-service.js'
|
|
101
110
|
export type {
|
|
@@ -114,9 +123,35 @@ export type { GatewayService } from './services/gateway-service.js'
|
|
|
114
123
|
export type { TriggerService } from './services/trigger-service.js'
|
|
115
124
|
export type { SchemaService } from './services/schema-service.js'
|
|
116
125
|
export type { SessionService } from './services/user-session-service.js'
|
|
126
|
+
export {
|
|
127
|
+
NoopAuditService,
|
|
128
|
+
createInvocationAudit,
|
|
129
|
+
resolveAuditActorFromWire,
|
|
130
|
+
resolveAuditConfig,
|
|
131
|
+
} from './services/audit-service.js'
|
|
132
|
+
export type {
|
|
133
|
+
AuditActor,
|
|
134
|
+
AuditConfig,
|
|
135
|
+
AuditDurability,
|
|
136
|
+
AuditEvent,
|
|
137
|
+
AuditEventBatch,
|
|
138
|
+
AuditLog,
|
|
139
|
+
AuditLogWriteInput,
|
|
140
|
+
AuditOutcome,
|
|
141
|
+
AuditService,
|
|
142
|
+
AuditSource,
|
|
143
|
+
ResolvedAuditConfig,
|
|
144
|
+
} from './services/audit-service.js'
|
|
117
145
|
export type { AIAgentRunnerService } from './services/ai-agent-runner-service.js'
|
|
118
146
|
export type { AIRunStateService } from './services/ai-run-state-service.js'
|
|
119
147
|
export type { AIStorageService } from './services/ai-storage-service.js'
|
|
148
|
+
export type {
|
|
149
|
+
EmailsMeta,
|
|
150
|
+
EmailTemplateMeta,
|
|
151
|
+
EmailTemplateLocaleMeta,
|
|
152
|
+
EmailTemplateAssets,
|
|
153
|
+
MetaService,
|
|
154
|
+
} from './services/meta-service.js'
|
|
120
155
|
export type { HTTPMethod } from './wirings/http/http.types.js'
|
|
121
156
|
export type { GraphNodeConfig } from './wirings/workflow/graph/workflow-graph.types.js'
|
|
122
157
|
export { createGraph } from './wirings/workflow/graph/graph-node.js'
|
|
@@ -142,3 +177,12 @@ export {
|
|
|
142
177
|
type ScheduledTaskSummary,
|
|
143
178
|
} from './services/scheduler-service.js'
|
|
144
179
|
export { SchedulerService } from './services/scheduler-service.js'
|
|
180
|
+
|
|
181
|
+
export type {
|
|
182
|
+
Private,
|
|
183
|
+
Secret,
|
|
184
|
+
Classification,
|
|
185
|
+
AnonymizeStrategy,
|
|
186
|
+
ColumnClassification,
|
|
187
|
+
ClassificationManifest,
|
|
188
|
+
} from './data-classification.js'
|
package/src/middleware-runner.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
CoreSingletonServices,
|
|
3
|
-
PikkuWire,
|
|
4
2
|
CorePikkuMiddleware,
|
|
5
3
|
CorePikkuMiddlewareGroup,
|
|
6
4
|
PikkuWiringTypes,
|
|
@@ -18,16 +16,16 @@ const PRIORITY_ORDER: Record<MiddlewarePriority, number> = {
|
|
|
18
16
|
lowest: 4,
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
const getMiddlewarePriority = (fn: CorePikkuMiddleware): number => {
|
|
19
|
+
const getMiddlewarePriority = (fn: CorePikkuMiddleware<any, any>): number => {
|
|
22
20
|
const priority = (
|
|
23
|
-
fn as CorePikkuMiddleware & { __priority?: MiddlewarePriority }
|
|
21
|
+
fn as CorePikkuMiddleware<any, any> & { __priority?: MiddlewarePriority }
|
|
24
22
|
).__priority
|
|
25
23
|
return PRIORITY_ORDER[priority ?? 'medium']
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
const sortByPriority = (
|
|
29
|
-
middlewares: CorePikkuMiddleware[]
|
|
30
|
-
): CorePikkuMiddleware[] => {
|
|
27
|
+
middlewares: CorePikkuMiddleware<any, any>[]
|
|
28
|
+
): CorePikkuMiddleware<any, any>[] => {
|
|
31
29
|
return middlewares.sort(
|
|
32
30
|
(a, b) => getMiddlewarePriority(a) - getMiddlewarePriority(b)
|
|
33
31
|
)
|
|
@@ -50,9 +48,11 @@ const sortByPriority = (
|
|
|
50
48
|
* async () => { return await runMain(); }
|
|
51
49
|
* );
|
|
52
50
|
*/
|
|
53
|
-
export const runMiddleware = async <
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
export const runMiddleware = async <
|
|
52
|
+
Middleware extends CorePikkuMiddleware<any, any>,
|
|
53
|
+
>(
|
|
54
|
+
services: Parameters<Middleware>[0],
|
|
55
|
+
wire: Parameters<Middleware>[1],
|
|
56
56
|
middlewares: readonly Middleware[],
|
|
57
57
|
main?: () => Promise<unknown>
|
|
58
58
|
): Promise<unknown> => {
|
|
@@ -77,7 +77,7 @@ export const runMiddleware = async <Middleware extends CorePikkuMiddleware>(
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const isSortedByPriority = (
|
|
80
|
-
middlewares: readonly CorePikkuMiddleware[]
|
|
80
|
+
middlewares: readonly CorePikkuMiddleware<any, any>[]
|
|
81
81
|
): boolean => {
|
|
82
82
|
for (let i = 1; i < middlewares.length; i++) {
|
|
83
83
|
if (
|
package/src/permissions.ts
CHANGED
|
@@ -24,7 +24,7 @@ const verifyPermissions = async <Out = any>(
|
|
|
24
24
|
permissions: CorePermissionGroup,
|
|
25
25
|
services: CoreServices,
|
|
26
26
|
data: any,
|
|
27
|
-
wire: PikkuWire<any, never, any, never, never, never>
|
|
27
|
+
wire: PikkuWire<any, never, any, CoreUserSession, never, never, never>
|
|
28
28
|
): Promise<boolean> => {
|
|
29
29
|
if (!permissions) {
|
|
30
30
|
return true
|
|
@@ -311,7 +311,7 @@ export const runPermissions = async (
|
|
|
311
311
|
funcInheritedPermissions?: PermissionMetadata[]
|
|
312
312
|
funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
313
313
|
services: CoreServices
|
|
314
|
-
wire: PikkuWire<any, never, any, never, never, never>
|
|
314
|
+
wire: PikkuWire<any, never, any, CoreUserSession, never, never, never>
|
|
315
315
|
data: any
|
|
316
316
|
packageName?: string | null
|
|
317
317
|
}
|
|
@@ -377,13 +377,13 @@ export const checkAuthPermissions = async (
|
|
|
377
377
|
any,
|
|
378
378
|
never,
|
|
379
379
|
any,
|
|
380
|
-
|
|
380
|
+
CoreUserSession,
|
|
381
381
|
never,
|
|
382
382
|
never
|
|
383
383
|
>
|
|
384
384
|
|
|
385
385
|
// Extract only pikkuAuth permissions (marked with __pikkuAuth)
|
|
386
|
-
const authPerms: CorePikkuPermission[] = []
|
|
386
|
+
const authPerms: CorePikkuPermission<any, any, any>[] = []
|
|
387
387
|
for (const permission of allPermissions) {
|
|
388
388
|
if (typeof permission === 'function') {
|
|
389
389
|
if ((permission as any).__pikkuAuth) {
|
|
@@ -42,4 +42,7 @@ export interface AIAgentRunnerService {
|
|
|
42
42
|
channel: AIStreamChannel
|
|
43
43
|
): Promise<AIAgentStepResult>
|
|
44
44
|
run(params: AIAgentRunnerParams): Promise<AIAgentStepResult>
|
|
45
|
+
/** Return a new runner that uses the given API key for every LLM call.
|
|
46
|
+
* Optional — runners that don't support per-key scoping leave this undefined. */
|
|
47
|
+
withApiKey?(apiKey: string): AIAgentRunnerService
|
|
45
48
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import {
|
|
4
|
+
createInvocationAudit,
|
|
5
|
+
type AuditEvent,
|
|
6
|
+
type AuditEventBatch,
|
|
7
|
+
} from './audit-service.js'
|
|
8
|
+
|
|
9
|
+
describe('audit-service', () => {
|
|
10
|
+
test('best-effort flush failures are logged and do not throw', async () => {
|
|
11
|
+
const warnings: unknown[][] = []
|
|
12
|
+
const audit = createInvocationAudit(
|
|
13
|
+
{
|
|
14
|
+
async audit(_event: AuditEvent): Promise<void> {
|
|
15
|
+
throw new Error('should not use single-event write')
|
|
16
|
+
},
|
|
17
|
+
async write(_batch: AuditEventBatch): Promise<void> {
|
|
18
|
+
throw new Error('sink failed')
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
wireType: 'rpc',
|
|
23
|
+
wireId: 'wire-best-effort',
|
|
24
|
+
functionId: 'bestEffortFunc',
|
|
25
|
+
audit: { durability: 'best-effort' },
|
|
26
|
+
logger: {
|
|
27
|
+
warn: (...args: unknown[]) => warnings.push(args),
|
|
28
|
+
},
|
|
29
|
+
} as any
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
await audit.write({
|
|
33
|
+
type: 'db.query',
|
|
34
|
+
source: 'auto',
|
|
35
|
+
queryId: 'q-1',
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
await assert.doesNotReject(async () => {
|
|
39
|
+
await audit.close()
|
|
40
|
+
})
|
|
41
|
+
assert.equal(warnings.length, 1)
|
|
42
|
+
assert.equal(warnings[0]?.[0], 'best-effort audit flush failed')
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CoreUserSession,
|
|
3
|
+
PikkuWire,
|
|
4
|
+
PikkuWiringTypes,
|
|
5
|
+
} from '../types/core.types.js'
|
|
6
|
+
|
|
7
|
+
export type AuditDurability = 'best-effort' | 'transactional'
|
|
8
|
+
export type AuditOutcome = 'success' | 'failed' | 'denied'
|
|
9
|
+
export type AuditSource = 'auto' | 'explicit'
|
|
10
|
+
|
|
11
|
+
export type AuditConfig =
|
|
12
|
+
| boolean
|
|
13
|
+
| {
|
|
14
|
+
durability?: AuditDurability
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type ResolvedAuditConfig = {
|
|
18
|
+
durability: AuditDurability
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type AuditActor = {
|
|
22
|
+
userId?: string
|
|
23
|
+
orgId?: string
|
|
24
|
+
pikkuUserId?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type AuditEvent = {
|
|
28
|
+
eventId?: string
|
|
29
|
+
type: string
|
|
30
|
+
source: AuditSource
|
|
31
|
+
outcome?: AuditOutcome
|
|
32
|
+
occurredAt: string
|
|
33
|
+
functionId?: string
|
|
34
|
+
wireType?: PikkuWiringTypes
|
|
35
|
+
wireId?: string
|
|
36
|
+
traceId?: string
|
|
37
|
+
transactionId?: string | null
|
|
38
|
+
queryId?: string | null
|
|
39
|
+
actor?: AuditActor
|
|
40
|
+
input?: unknown
|
|
41
|
+
metadata?: Record<string, unknown>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type AuditEventBatch = AuditEvent[]
|
|
45
|
+
|
|
46
|
+
export interface AuditService {
|
|
47
|
+
audit(event: AuditEvent): Promise<void>
|
|
48
|
+
write?(batch: AuditEventBatch): Promise<void>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class NoopAuditService implements AuditService {
|
|
52
|
+
async audit(_event: AuditEvent): Promise<void> {}
|
|
53
|
+
|
|
54
|
+
async write(_batch: AuditEventBatch): Promise<void> {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type AuditLogWriteInput = Omit<AuditEvent, 'occurredAt'>
|
|
58
|
+
|
|
59
|
+
export interface AuditLog {
|
|
60
|
+
readonly config: ResolvedAuditConfig | undefined
|
|
61
|
+
write(event: AuditLogWriteInput): Promise<void>
|
|
62
|
+
flush(): Promise<void>
|
|
63
|
+
close(): Promise<void>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class DisabledInvocationAudit implements AuditLog {
|
|
67
|
+
public readonly config = undefined
|
|
68
|
+
private warned = false
|
|
69
|
+
|
|
70
|
+
constructor(
|
|
71
|
+
private readonly wire: PikkuWire<any, any, any, CoreUserSession>
|
|
72
|
+
) {}
|
|
73
|
+
|
|
74
|
+
async write(_event: AuditLogWriteInput): Promise<void> {
|
|
75
|
+
if (!this.warned) {
|
|
76
|
+
this.warned = true
|
|
77
|
+
;(this.wire as any).logger?.warn?.(
|
|
78
|
+
'audit.write() called for an invocation without wire.audit enabled'
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async flush(): Promise<void> {}
|
|
84
|
+
|
|
85
|
+
async close(): Promise<void> {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class InvocationAuditLog implements AuditLog {
|
|
89
|
+
private readonly buffer: AuditEvent[] = []
|
|
90
|
+
|
|
91
|
+
constructor(
|
|
92
|
+
public readonly config: ResolvedAuditConfig,
|
|
93
|
+
private readonly service: AuditService,
|
|
94
|
+
private readonly wire: PikkuWire<any, any, any, CoreUserSession>
|
|
95
|
+
) {}
|
|
96
|
+
|
|
97
|
+
async write(event: AuditLogWriteInput): Promise<void> {
|
|
98
|
+
const resolved = this.resolveEvent(event)
|
|
99
|
+
|
|
100
|
+
if (this.config.durability === 'transactional') {
|
|
101
|
+
await this.service.audit(resolved)
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.buffer.push(resolved)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async flush(): Promise<void> {
|
|
109
|
+
if (
|
|
110
|
+
this.config.durability === 'transactional' ||
|
|
111
|
+
this.buffer.length === 0
|
|
112
|
+
) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const batch = this.buffer.splice(0, this.buffer.length)
|
|
117
|
+
const queryEvents = batch.filter((event) => event.queryId != null)
|
|
118
|
+
if (queryEvents.length === 1) {
|
|
119
|
+
queryEvents[0]!.queryId = null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
if (this.service.write) {
|
|
124
|
+
await this.service.write(batch)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const event of batch) {
|
|
129
|
+
await this.service.audit(event)
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
;(this.wire as any).logger?.warn?.(
|
|
133
|
+
'best-effort audit flush failed',
|
|
134
|
+
error
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async close(): Promise<void> {
|
|
140
|
+
await this.flush()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private resolveEvent(event: AuditLogWriteInput): AuditEvent {
|
|
144
|
+
return {
|
|
145
|
+
functionId: this.wire.functionId,
|
|
146
|
+
wireType: this.wire.wireType,
|
|
147
|
+
wireId: this.wire.wireId,
|
|
148
|
+
traceId: this.wire.traceId,
|
|
149
|
+
actor: event.actor ?? resolveAuditActorFromWire(this.wire),
|
|
150
|
+
...event,
|
|
151
|
+
occurredAt: new Date().toISOString(),
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const resolveAuditConfig = (
|
|
157
|
+
config?: AuditConfig
|
|
158
|
+
): ResolvedAuditConfig | undefined => {
|
|
159
|
+
if (config === true) {
|
|
160
|
+
return { durability: 'best-effort' }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!config) {
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
durability: config.durability ?? 'best-effort',
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const createInvocationAudit = (
|
|
173
|
+
service: AuditService,
|
|
174
|
+
wire: PikkuWire<any, any, any, CoreUserSession>
|
|
175
|
+
): AuditLog => {
|
|
176
|
+
if (!wire.audit) {
|
|
177
|
+
return new DisabledInvocationAudit(wire)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return new InvocationAuditLog(wire.audit, service, wire)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const resolveAuditActorFromWire = (
|
|
184
|
+
wire: PikkuWire<any, any, any, CoreUserSession>
|
|
185
|
+
): AuditActor | undefined => {
|
|
186
|
+
const session = wire.session as CoreUserSession | undefined
|
|
187
|
+
const actor: AuditActor = {
|
|
188
|
+
userId: session?.userId,
|
|
189
|
+
orgId: session?.orgId,
|
|
190
|
+
pikkuUserId: wire.pikkuUserId,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!actor.userId && !actor.orgId && !actor.pikkuUserId) {
|
|
194
|
+
return undefined
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return actor
|
|
198
|
+
}
|