@pikku/core 0.12.80 → 0.12.83
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 +345 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/function/function-runner.js +2 -5
- package/dist/function/index.d.ts +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +3 -3
- package/dist/pikku-state.js +4 -0
- package/dist/services/ai-agent-runner-service.d.ts +7 -0
- package/dist/services/ai-run-state-service.d.ts +10 -0
- package/dist/services/in-memory-ai-run-state-service.d.ts +5 -1
- package/dist/services/in-memory-ai-run-state-service.js +9 -0
- package/dist/services/index.d.ts +15 -16
- package/dist/services/index.js +5 -5
- package/dist/services/meta-service.d.ts +2 -1
- package/dist/services/scoped-credential-service.d.ts +21 -0
- package/dist/services/scoped-credential-service.js +53 -0
- package/dist/testing/service-tests/ai-storage-service-tests.js +76 -0
- package/dist/types/core.types.d.ts +2 -3
- package/dist/types/state.types.d.ts +19 -1
- package/dist/wirings/actor-flow/index.d.ts +1 -1
- package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +58 -0
- package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
- package/dist/wirings/ai-agent/ai-agent-memory.js +34 -17
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
- package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -0
- package/dist/wirings/ai-agent/ai-agent-runner.js +61 -40
- package/dist/wirings/ai-agent/ai-agent-stream.js +89 -36
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +1 -0
- package/dist/wirings/ai-agent/ai-agent-turn.js +1 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +46 -1
- package/dist/wirings/ai-agent/index.d.ts +8 -7
- package/dist/wirings/ai-agent/index.js +5 -4
- package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
- package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
- package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
- package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
- package/dist/wirings/ai-scorer/index.d.ts +6 -0
- package/dist/wirings/ai-scorer/index.js +5 -0
- package/dist/wirings/channel/index.d.ts +5 -6
- package/dist/wirings/channel/index.js +3 -4
- package/dist/wirings/channel/local/local-channel-runner.js +8 -1
- package/dist/wirings/cli/channel/cli-raw-channel-runner.js +9 -1
- package/dist/wirings/cli/channel/index.d.ts +1 -2
- package/dist/wirings/cli/channel/index.js +0 -1
- package/dist/wirings/cli/cli-runner.js +13 -1
- package/dist/wirings/credential/index.d.ts +1 -1
- package/dist/wirings/gateway/index.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +8 -2
- package/dist/wirings/http/index.d.ts +1 -2
- package/dist/wirings/mcp/index.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +15 -0
- package/dist/wirings/mcp/mcp-runner.js +18 -5
- package/dist/wirings/persona/index.d.ts +3 -4
- package/dist/wirings/persona/index.js +2 -3
- package/dist/wirings/queue/index.d.ts +1 -3
- package/dist/wirings/queue/index.js +1 -3
- package/dist/wirings/rpc/addon-runner.d.ts +8 -0
- package/dist/wirings/rpc/addon-runner.js +31 -3
- package/dist/wirings/rpc/rpc-runner.js +4 -0
- package/dist/wirings/rpc/rpc-types.d.ts +8 -0
- package/dist/wirings/rpc/wire-addon.d.ts +25 -0
- package/dist/wirings/rpc/wire-addon.js +8 -0
- package/dist/wirings/scheduler/index.d.ts +1 -1
- package/dist/wirings/trigger/index.d.ts +1 -1
- package/dist/wirings/virtual-user/index.d.ts +5 -6
- package/dist/wirings/virtual-user/index.js +2 -4
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +85 -15
- package/dist/wirings/workflow/feature.d.ts +2 -1
- package/dist/wirings/workflow/index.d.ts +5 -16
- package/dist/wirings/workflow/index.js +1 -9
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -7
- package/dist/wirings/workflow/pikku-scenario-service.js +48 -13
- package/dist/wirings/workflow/pikku-workflow-service.js +17 -3
- package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
- package/dist/wirings/workflow/scenario.types.d.ts +37 -0
- package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
- package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
- package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
- package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
- package/dist/wirings/workflow/workflow-approval.d.ts +29 -1
- package/dist/wirings/workflow/workflow-approval.js +65 -2
- package/dist/wirings/workflow/workflow-run-ownership.d.ts +2 -1
- package/dist/wirings/workflow/workflow-run-ownership.js +2 -1
- package/dist/wirings/workflow/workflow.types.d.ts +2 -37
- package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -0
- package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -0
- package/knowledge/decisions/internals/index.md +30 -3
- package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +115 -0
- package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +37 -0
- package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +30 -14
- package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -0
- package/knowledge/decisions/security/index.md +3 -1
- package/knowledge/questions/index.md +1 -1
- package/package.json +3 -2
- package/scripts/generate-api-report.mts +143 -18
- package/src/api-report.test.ts +2 -2
- package/src/errors/index.ts +1 -1
- package/src/function/function-runner.test.ts +52 -0
- package/src/function/function-runner.ts +5 -9
- package/src/function/index.ts +0 -2
- package/src/index.ts +0 -35
- package/src/pikku-state.ts +5 -0
- package/src/public-surface.json +81 -118
- package/src/services/ai-agent-runner-service.ts +12 -1
- package/src/services/ai-run-state-service.ts +11 -0
- package/src/services/in-memory-ai-run-state-service.ts +13 -0
- package/src/services/index.ts +7 -58
- package/src/services/meta-service.ts +2 -4
- package/src/services/scoped-credential-service.test.ts +86 -0
- package/src/services/scoped-credential-service.ts +63 -0
- package/src/testing/service-tests/ai-storage-service-tests.ts +93 -0
- package/src/types/core.types.ts +4 -7
- package/src/types/state.types.ts +21 -1
- package/src/wirings/actor-flow/index.ts +0 -3
- package/src/wirings/ai-agent/ai-agent-finalize.test.ts +186 -0
- package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +54 -38
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
- package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
- package/src/wirings/ai-agent/ai-agent-prepare.ts +4 -0
- package/src/wirings/ai-agent/ai-agent-runner.ts +71 -40
- package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +116 -54
- package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
- package/src/wirings/ai-agent/ai-agent-turn.ts +1 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +64 -4
- package/src/wirings/ai-agent/index.ts +2 -16
- package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
- package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
- package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
- package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
- package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
- package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
- package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
- package/src/wirings/ai-scorer/index.ts +24 -0
- package/src/wirings/channel/index.ts +1 -20
- package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
- package/src/wirings/channel/local/local-channel-runner.ts +8 -1
- package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +23 -0
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
- package/src/wirings/cli/channel/index.ts +0 -7
- package/src/wirings/cli/cli-runner.test.ts +68 -0
- package/src/wirings/cli/cli-runner.ts +18 -1
- package/src/wirings/credential/index.ts +0 -1
- package/src/wirings/gateway/index.ts +0 -3
- package/src/wirings/http/http-runner.test.ts +66 -0
- package/src/wirings/http/http-runner.ts +10 -2
- package/src/wirings/http/index.ts +1 -1
- package/src/wirings/mcp/index.ts +0 -1
- package/src/wirings/mcp/mcp-runner.test.ts +181 -0
- package/src/wirings/mcp/mcp-runner.ts +35 -5
- package/src/wirings/persona/index.ts +0 -8
- package/src/wirings/queue/index.ts +0 -14
- package/src/wirings/rpc/addon-runner.ts +62 -3
- package/src/wirings/rpc/addon-secrets.test.ts +391 -0
- package/src/wirings/rpc/rpc-runner.test.ts +2 -0
- package/src/wirings/rpc/rpc-runner.ts +4 -0
- package/src/wirings/rpc/rpc-types.ts +8 -0
- package/src/wirings/rpc/wire-addon.ts +33 -0
- package/src/wirings/scheduler/index.ts +0 -1
- package/src/wirings/trigger/index.ts +0 -1
- package/src/wirings/virtual-user/index.ts +0 -16
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +96 -16
- package/src/wirings/workflow/feature.ts +2 -5
- package/src/wirings/workflow/graph/graph-runner.test.ts +72 -0
- package/src/wirings/workflow/index.ts +2 -68
- package/src/wirings/workflow/pikku-scenario-service.ts +81 -16
- package/src/wirings/workflow/pikku-workflow-service.test.ts +13 -12
- package/src/wirings/workflow/pikku-workflow-service.ts +28 -4
- package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
- package/src/wirings/workflow/scenario-hooks.test.ts +3 -2
- package/src/wirings/workflow/scenario-step.types.ts +8 -0
- package/src/wirings/workflow/scenario.types.ts +63 -0
- package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
- package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
- package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
- package/src/wirings/workflow/workflow-approval.ts +113 -9
- package/src/wirings/workflow/workflow-run-authority.test.ts +12 -15
- package/src/wirings/workflow/workflow-run-ownership.ts +2 -1
- package/src/wirings/workflow/workflow.types.ts +1 -63
- package/src/wirings-stay-decoupled.test.ts +6 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/internal.d.ts +0 -3
- package/dist/internal.js +0 -2
- package/dist/middleware/timeout.d.ts +0 -9
- package/dist/middleware/timeout.js +0 -15
- package/dist/pikku-response.d.ts +0 -6
- package/dist/pikku-response.js +0 -6
- package/dist/services/gopass-secrets.d.ts +0 -15
- package/dist/services/gopass-secrets.js +0 -76
- package/dist/services/http-scenario-actors.d.ts +0 -75
- package/dist/services/http-scenario-actors.js +0 -195
- package/dist/services/http-user-flow-actors.d.ts +0 -67
- package/dist/services/http-user-flow-actors.js +0 -193
- package/dist/services/scenario-actors-service.d.ts +0 -127
- package/dist/services/scenario-actors-service.js +0 -40
- package/dist/services/user-flow-actors-service.d.ts +0 -39
- package/dist/wirings/credential/wire-credential.d.ts +0 -48
- package/dist/wirings/credential/wire-credential.js +0 -47
- package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
- package/dist/wirings/oauth2/oauth2-client.js +0 -263
- package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
- package/dist/wirings/oauth2/oauth2-routes.js +0 -146
- package/dist/wirings/scope/wire-scope.d.ts +0 -33
- package/dist/wirings/scope/wire-scope.js +0 -32
- package/dist/wirings/workflow/dsl/index.d.ts +0 -5
- package/dist/wirings/workflow/dsl/index.js +0 -4
- package/dist/wirings/workflow/graph/index.d.ts +0 -5
- package/dist/wirings/workflow/graph/index.js +0 -4
- /package/dist/{services/user-flow-actors-service.js → wirings/workflow/scenario.types.js} +0 -0
|
@@ -8,6 +8,14 @@ import type {
|
|
|
8
8
|
StepState,
|
|
9
9
|
WorkflowApprovalOptions,
|
|
10
10
|
} from './workflow.types.js'
|
|
11
|
+
import type {
|
|
12
|
+
ApprovalDecider,
|
|
13
|
+
WorkflowApprovalPolicy,
|
|
14
|
+
} from './dsl/workflow-dsl.types.js'
|
|
15
|
+
import {
|
|
16
|
+
approvalPolicyRefusal,
|
|
17
|
+
WorkflowApprovalForbiddenError,
|
|
18
|
+
} from './workflow-approval-policy.js'
|
|
11
19
|
|
|
12
20
|
/** The durable step name an approval point is recorded under. */
|
|
13
21
|
export const approvalStepNameFor = (reason: string): string =>
|
|
@@ -28,6 +36,21 @@ export const approvalStateKey = (stepName: string): string => {
|
|
|
28
36
|
return `__approval_${hex}`
|
|
29
37
|
}
|
|
30
38
|
|
|
39
|
+
/**
|
|
40
|
+
* An answer to an approval gate, as the audit trail sees it.
|
|
41
|
+
*
|
|
42
|
+
* `denied` covers both a submission refused at the door and a decision cleared
|
|
43
|
+
* on replay — from the trail's point of view they are the same event, someone
|
|
44
|
+
* tried to answer a gate they could not.
|
|
45
|
+
*/
|
|
46
|
+
export type ApprovalAuditEvent = {
|
|
47
|
+
runId: string
|
|
48
|
+
reason: string
|
|
49
|
+
outcome: 'success' | 'denied'
|
|
50
|
+
decidedBy?: ApprovalDecider
|
|
51
|
+
refusal?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
/** What the approval gate needs from the workflow service. */
|
|
32
55
|
export type ApprovalStore = {
|
|
33
56
|
getStepState: (runId: string, stepName: string) => Promise<StepState>
|
|
@@ -45,6 +68,18 @@ export type ApprovalStore = {
|
|
|
45
68
|
updateRunState: (runId: string, key: string, value: unknown) => Promise<void>
|
|
46
69
|
resumeWorkflow: (runId: string) => Promise<void>
|
|
47
70
|
scheduleRunWake: (runId: string, delay: number) => Promise<void>
|
|
71
|
+
getRunOwner: (runId: string) => Promise<string | undefined>
|
|
72
|
+
auditApproval: (event: ApprovalAuditEvent) => Promise<void>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** What run state holds for an approval point between suspension and replay. */
|
|
76
|
+
type ApprovalRecord = {
|
|
77
|
+
decision?: unknown
|
|
78
|
+
decidedAt?: string
|
|
79
|
+
decidedBy?: ApprovalDecider
|
|
80
|
+
policy?: WorkflowApprovalPolicy
|
|
81
|
+
expiresAt?: string
|
|
82
|
+
error?: unknown
|
|
48
83
|
}
|
|
49
84
|
|
|
50
85
|
/**
|
|
@@ -53,12 +88,19 @@ export type ApprovalStore = {
|
|
|
53
88
|
* A decision arriving for an already-settled approval is refused rather than
|
|
54
89
|
* overwriting it: the run has moved on, and a second answer would be recorded
|
|
55
90
|
* against a gate nobody is waiting at.
|
|
91
|
+
*
|
|
92
|
+
* The decider is recorded rather than judged here, for the same reason the
|
|
93
|
+
* payload is: the gate's policy is a value on the workflow, and a decision can
|
|
94
|
+
* legitimately arrive before the run has ever reached the gate. Where the run
|
|
95
|
+
* has already published its policy, it is applied eagerly so a caller who
|
|
96
|
+
* cannot answer is told so rather than left waiting.
|
|
56
97
|
*/
|
|
57
98
|
export const recordApprovalDecision = async (
|
|
58
99
|
store: ApprovalStore,
|
|
59
100
|
runId: string,
|
|
60
101
|
reason: string,
|
|
61
|
-
decision: unknown
|
|
102
|
+
decision: unknown,
|
|
103
|
+
decidedBy?: ApprovalDecider
|
|
62
104
|
): Promise<void> => {
|
|
63
105
|
const stepName = approvalStepNameFor(reason)
|
|
64
106
|
const stateKey = approvalStateKey(stepName)
|
|
@@ -78,13 +120,34 @@ export const recordApprovalDecision = async (
|
|
|
78
120
|
}
|
|
79
121
|
|
|
80
122
|
const state = await store.getRunState(runId)
|
|
81
|
-
const record = (state[stateKey] ?? {}) as
|
|
123
|
+
const record = (state[stateKey] ?? {}) as ApprovalRecord
|
|
124
|
+
|
|
125
|
+
if (record.policy) {
|
|
126
|
+
const refusal = approvalPolicyRefusal(
|
|
127
|
+
record.policy,
|
|
128
|
+
await store.getRunOwner(runId),
|
|
129
|
+
decidedBy
|
|
130
|
+
)
|
|
131
|
+
if (refusal) {
|
|
132
|
+
await store.auditApproval({
|
|
133
|
+
runId,
|
|
134
|
+
reason,
|
|
135
|
+
outcome: 'denied',
|
|
136
|
+
decidedBy,
|
|
137
|
+
refusal,
|
|
138
|
+
})
|
|
139
|
+
throw new WorkflowApprovalForbiddenError(reason, refusal)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
82
143
|
await store.updateRunState(runId, stateKey, {
|
|
83
144
|
...record,
|
|
84
145
|
decision,
|
|
85
146
|
decidedAt: new Date().toISOString(),
|
|
147
|
+
decidedBy,
|
|
86
148
|
error: undefined,
|
|
87
|
-
})
|
|
149
|
+
} satisfies ApprovalRecord)
|
|
150
|
+
await store.auditApproval({ runId, reason, outcome: 'success', decidedBy })
|
|
88
151
|
await store.resumeWorkflow(runId)
|
|
89
152
|
}
|
|
90
153
|
|
|
@@ -95,6 +158,11 @@ export const recordApprovalDecision = async (
|
|
|
95
158
|
* schema is a value on the workflow and only the workflow has it. A payload
|
|
96
159
|
* that fails validation is cleared and the run suspends again, so a bad
|
|
97
160
|
* submission cannot settle the gate.
|
|
161
|
+
*
|
|
162
|
+
* The decider is judged here for the same reason, and for one more: a decision
|
|
163
|
+
* can be recorded before the run has ever reached the gate, so submission time
|
|
164
|
+
* is not a point at which the policy is reliably knowable. Judging on replay is
|
|
165
|
+
* what makes the policy hold in every ordering.
|
|
98
166
|
*/
|
|
99
167
|
export const evaluateApprovalStep = async (
|
|
100
168
|
store: ApprovalStore,
|
|
@@ -109,7 +177,12 @@ export const evaluateApprovalStep = async (
|
|
|
109
177
|
runId,
|
|
110
178
|
approvalStepName,
|
|
111
179
|
'pikkuWorkflowApproval',
|
|
112
|
-
{
|
|
180
|
+
{
|
|
181
|
+
reason,
|
|
182
|
+
expiry: options.expiry,
|
|
183
|
+
approvers: options.approvers,
|
|
184
|
+
approverScope: options.approverScope,
|
|
185
|
+
},
|
|
113
186
|
undefined,
|
|
114
187
|
fromStepName
|
|
115
188
|
)
|
|
@@ -129,15 +202,16 @@ export const evaluateApprovalStep = async (
|
|
|
129
202
|
}
|
|
130
203
|
|
|
131
204
|
const stateKey = approvalStateKey(approvalStepName)
|
|
132
|
-
let record = ((await store.getRunState(runId))[stateKey] ?? {}) as
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
205
|
+
let record = ((await store.getRunState(runId))[stateKey] ?? {}) as ApprovalRecord
|
|
206
|
+
|
|
207
|
+
const policy: WorkflowApprovalPolicy = {
|
|
208
|
+
approvers: options.approvers,
|
|
209
|
+
approverScope: options.approverScope,
|
|
137
210
|
}
|
|
138
211
|
|
|
139
212
|
if (stepState.status === 'pending') {
|
|
140
213
|
await store.setStepRunning(stepState.stepId)
|
|
214
|
+
record = { ...record, policy }
|
|
141
215
|
if (options.expiry !== undefined && !record.expiresAt) {
|
|
142
216
|
const expiry = getDurationInMilliseconds(options.expiry)
|
|
143
217
|
record = {
|
|
@@ -146,10 +220,35 @@ export const evaluateApprovalStep = async (
|
|
|
146
220
|
}
|
|
147
221
|
await store.updateRunState(runId, stateKey, record)
|
|
148
222
|
await store.scheduleRunWake(runId, expiry)
|
|
223
|
+
} else {
|
|
224
|
+
await store.updateRunState(runId, stateKey, record)
|
|
149
225
|
}
|
|
150
226
|
}
|
|
151
227
|
|
|
152
228
|
if (record.decision !== undefined) {
|
|
229
|
+
const refusal = approvalPolicyRefusal(
|
|
230
|
+
policy,
|
|
231
|
+
await store.getRunOwner(runId),
|
|
232
|
+
record.decidedBy
|
|
233
|
+
)
|
|
234
|
+
if (refusal) {
|
|
235
|
+
await store.updateRunState(runId, stateKey, {
|
|
236
|
+
...record,
|
|
237
|
+
decision: undefined,
|
|
238
|
+
decidedAt: undefined,
|
|
239
|
+
decidedBy: undefined,
|
|
240
|
+
error: [{ message: refusal }],
|
|
241
|
+
} satisfies ApprovalRecord)
|
|
242
|
+
await store.auditApproval({
|
|
243
|
+
runId,
|
|
244
|
+
reason,
|
|
245
|
+
outcome: 'denied',
|
|
246
|
+
decidedBy: record.decidedBy,
|
|
247
|
+
refusal,
|
|
248
|
+
})
|
|
249
|
+
throw new WorkflowSuspendedException(runId, reason)
|
|
250
|
+
}
|
|
251
|
+
|
|
153
252
|
const validation = await options.schema['~standard'].validate(
|
|
154
253
|
record.decision
|
|
155
254
|
)
|
|
@@ -158,6 +257,7 @@ export const evaluateApprovalStep = async (
|
|
|
158
257
|
...record,
|
|
159
258
|
decision: undefined,
|
|
160
259
|
decidedAt: undefined,
|
|
260
|
+
decidedBy: undefined,
|
|
161
261
|
error: validation.issues.map((issue) => ({
|
|
162
262
|
message: issue.message,
|
|
163
263
|
path: issue.path?.map((segment) =>
|
|
@@ -167,9 +267,13 @@ export const evaluateApprovalStep = async (
|
|
|
167
267
|
})
|
|
168
268
|
throw new WorkflowSuspendedException(runId, reason)
|
|
169
269
|
}
|
|
270
|
+
// Spread rather than assigned, so a gate answered without a session keeps
|
|
271
|
+
// the shape it had before there was anything to record.
|
|
170
272
|
const outcome: ApprovalOutcome<unknown> = {
|
|
171
273
|
status: 'decided',
|
|
172
274
|
data: validation.value,
|
|
275
|
+
...(record.decidedBy ? { decidedBy: record.decidedBy } : {}),
|
|
276
|
+
...(record.decidedAt ? { decidedAt: record.decidedAt } : {}),
|
|
173
277
|
}
|
|
174
278
|
await store.setStepResult(stepState.stepId, outcome)
|
|
175
279
|
return outcome
|
|
@@ -160,7 +160,7 @@ describe('workflow run ownership', () => {
|
|
|
160
160
|
)
|
|
161
161
|
})
|
|
162
162
|
|
|
163
|
-
test('approveStep
|
|
163
|
+
test('approveStep does not impose ownership on a gate that did not ask for it', async () => {
|
|
164
164
|
seedSingletons()
|
|
165
165
|
const ws = new InMemoryWorkflowService()
|
|
166
166
|
const runId = await ws.createRun('flow', {}, false, 'hash', {
|
|
@@ -168,23 +168,20 @@ describe('workflow run ownership', () => {
|
|
|
168
168
|
pikkuUserId: 'owner',
|
|
169
169
|
})
|
|
170
170
|
|
|
171
|
-
await
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
),
|
|
180
|
-
ForbiddenError
|
|
171
|
+
await ws.approveStep(
|
|
172
|
+
runId,
|
|
173
|
+
'release-funds',
|
|
174
|
+
{ ok: true },
|
|
175
|
+
{
|
|
176
|
+
userId: 'not-the-owner',
|
|
177
|
+
}
|
|
181
178
|
)
|
|
182
179
|
|
|
183
180
|
const state = await ws.getRunState(runId)
|
|
184
|
-
assert.
|
|
185
|
-
Object.keys(state),
|
|
186
|
-
|
|
187
|
-
'
|
|
181
|
+
assert.equal(
|
|
182
|
+
Object.keys(state).length,
|
|
183
|
+
1,
|
|
184
|
+
'the decision was recorded, to be judged against the gate on replay'
|
|
188
185
|
)
|
|
189
186
|
})
|
|
190
187
|
|
|
@@ -15,7 +15,8 @@ addError(WorkflowRunForbiddenError, {
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* A run started through a session records that session's user as its owner, and
|
|
18
|
-
* only that user may read it
|
|
18
|
+
* only that user may read it. Who may *answer* a run's approval gates is the
|
|
19
|
+
* gate's own declaration — see `approvers` on `workflow.approval()`.
|
|
19
20
|
*
|
|
20
21
|
* A run with no recorded owner — started by a trigger, a scheduler, or a route
|
|
21
22
|
* wired without auth — has nobody to compare a caller against, so ownership is
|
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
import type { SerializedError, CommonWireMeta } from '../../types/core.types.js'
|
|
2
|
-
import type {
|
|
3
|
-
CorePikkuFunctionConfig,
|
|
4
|
-
CorePikkuFunctionHook,
|
|
5
|
-
} from '../../function/functions.types.js'
|
|
2
|
+
import type { CorePikkuFunctionConfig } from '../../function/functions.types.js'
|
|
6
3
|
import type { GroupConcurrencyConfig } from '../queue/queue.types.js'
|
|
7
4
|
|
|
8
5
|
export type { WorkflowService } from '../../services/workflow-service.js'
|
|
9
6
|
|
|
10
7
|
export type {
|
|
11
8
|
WorkflowStepOptions,
|
|
12
|
-
WorkflowExpectEventuallyOptions,
|
|
13
|
-
WorkflowExpectErrorOptions,
|
|
14
|
-
WorkflowExpectServiceOptions,
|
|
15
9
|
WorkflowWireDoRPC,
|
|
16
|
-
WorkflowWireDoInline,
|
|
17
|
-
WorkflowWireSleep,
|
|
18
|
-
WorkflowWireSuspend,
|
|
19
|
-
WorkflowWireApproval,
|
|
20
10
|
WorkflowApprovalOptions,
|
|
21
11
|
ApprovalOutcome,
|
|
22
12
|
InputSource,
|
|
23
13
|
OutputBinding,
|
|
24
14
|
RpcStepMeta,
|
|
25
|
-
SimpleCondition,
|
|
26
15
|
Condition,
|
|
27
|
-
BranchCase,
|
|
28
16
|
BranchStepMeta,
|
|
29
17
|
ParallelGroupStepMeta,
|
|
30
18
|
FanoutStepMeta,
|
|
@@ -39,22 +27,11 @@ export type {
|
|
|
39
27
|
SwitchStepMeta,
|
|
40
28
|
FilterStepMeta,
|
|
41
29
|
ArrayPredicateStepMeta,
|
|
42
|
-
ScenarioStepInvocation,
|
|
43
|
-
ScenarioStepMeta,
|
|
44
30
|
WorkflowStepMeta,
|
|
45
31
|
WorkflowStepWire,
|
|
46
32
|
PikkuWorkflowWire,
|
|
47
|
-
PikkuScenarioWire,
|
|
48
33
|
} from './dsl/workflow-dsl.types.js'
|
|
49
34
|
|
|
50
|
-
export type {
|
|
51
|
-
ScenarioStepPhase,
|
|
52
|
-
ScenarioStepOptions,
|
|
53
|
-
PikkuScenarioStepWire,
|
|
54
|
-
PikkuBrowserWire,
|
|
55
|
-
ScenarioBrowserProvider,
|
|
56
|
-
} from './scenario-step.types.js'
|
|
57
|
-
|
|
58
35
|
import type { WorkflowStepMeta } from './dsl/workflow-dsl.types.js'
|
|
59
36
|
|
|
60
37
|
export interface WorkflowRunWire {
|
|
@@ -247,45 +224,6 @@ export type CoreWorkflow<
|
|
|
247
224
|
tags?: string[]
|
|
248
225
|
}
|
|
249
226
|
|
|
250
|
-
export type CoreFeatureScenario =
|
|
251
|
-
| CorePikkuFunctionConfig<any, any, any>
|
|
252
|
-
| { scenario: CorePikkuFunctionConfig<any, any, any>; data: unknown }
|
|
253
|
-
|
|
254
|
-
export type CoreFeature = {
|
|
255
|
-
name: string
|
|
256
|
-
description?: string
|
|
257
|
-
tags?: string[]
|
|
258
|
-
scenarios: readonly CoreFeatureScenario[]
|
|
259
|
-
before?: CorePikkuFunctionHook
|
|
260
|
-
after?: CorePikkuFunctionHook
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export type FeatureMetaEntry = {
|
|
264
|
-
scenario: string
|
|
265
|
-
data?: unknown
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export type FeatureMeta = {
|
|
269
|
-
id: string
|
|
270
|
-
name: string
|
|
271
|
-
description?: string
|
|
272
|
-
tags: string[]
|
|
273
|
-
entries: FeatureMetaEntry[]
|
|
274
|
-
unresolvedEntries: number
|
|
275
|
-
hasBefore: boolean
|
|
276
|
-
hasAfter: boolean
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export type FeaturesMeta = Record<string, FeatureMeta>
|
|
280
|
-
|
|
281
|
-
export type FeaturePlanEntry = {
|
|
282
|
-
featureId: string
|
|
283
|
-
featureName: string
|
|
284
|
-
scenarioName: string
|
|
285
|
-
data?: unknown
|
|
286
|
-
tags: string[]
|
|
287
|
-
}
|
|
288
|
-
|
|
289
227
|
export interface PikkuWorkflow {
|
|
290
228
|
start: <I>(input: I) => Promise<{ runId: string }>
|
|
291
229
|
getRun: (runId: string) => Promise<WorkflowRun>
|
|
@@ -24,8 +24,12 @@ const ALLOWED: Record<string, string[]> = {
|
|
|
24
24
|
// `remote` on a wire with no peer — one leaf module, not the channel runtime.
|
|
25
25
|
http: ['channel'],
|
|
26
26
|
cli: ['channel'],
|
|
27
|
-
// An agent streams over a channel and is invoked over rpc
|
|
28
|
-
|
|
27
|
+
// An agent streams over a channel and is invoked over rpc, and grades itself
|
|
28
|
+
// as the last thing a finished run does.
|
|
29
|
+
'ai-agent': ['channel', 'rpc', 'ai-scorer'],
|
|
30
|
+
// A judge is a degenerate agent — it builds agent messages and runs them —
|
|
31
|
+
// and a grade is dispatched to a queue.
|
|
32
|
+
'ai-scorer': ['ai-agent', 'queue'],
|
|
29
33
|
// `wire.rpc.agent` — the facade lives with the agent runtime it delegates to.
|
|
30
34
|
rpc: ['ai-agent'],
|
|
31
35
|
gateway: ['http'],
|