@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
|
@@ -65,6 +65,20 @@ export interface WorkflowExpectServiceOptions extends WorkflowStepOptions {
|
|
|
65
65
|
times?: number
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/** Options for workflow.expectScore() */
|
|
69
|
+
export interface WorkflowExpectScoreOptions extends WorkflowStepOptions {
|
|
70
|
+
/** Fail below this score. Defaults to 0.5 — a scorer that answered at all. */
|
|
71
|
+
atLeast?: number
|
|
72
|
+
/** Fail above this score, for a scorer where high is the failure. */
|
|
73
|
+
atMost?: number
|
|
74
|
+
/**
|
|
75
|
+
* The known-correct answer, which is what a reference-based judge grades
|
|
76
|
+
* against. Live traffic has no answer key, so a judge declared
|
|
77
|
+
* `requiresReference` is only ever reachable from a scenario.
|
|
78
|
+
*/
|
|
79
|
+
reference?: string
|
|
80
|
+
}
|
|
81
|
+
|
|
68
82
|
/**
|
|
69
83
|
* Type signature for workflow.do() RPC form - used by inspector
|
|
70
84
|
*/
|
|
@@ -114,12 +128,55 @@ export type WorkflowWireSleep = (
|
|
|
114
128
|
*/
|
|
115
129
|
export type WorkflowWireSuspend = (reason: string) => Promise<void>
|
|
116
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Who is allowed to answer an approval gate, relative to the user who started
|
|
133
|
+
* the run.
|
|
134
|
+
*
|
|
135
|
+
* - `any` — anyone the approve entrypoint lets through. The gate is a pause for
|
|
136
|
+
* a decision, not an authorization boundary.
|
|
137
|
+
* - `owner` — only the user who started the run, so a gate can be used to
|
|
138
|
+
* confirm an action against the same person who requested it.
|
|
139
|
+
* - `not-initiator` — anyone *except* the user who started the run: four-eyes.
|
|
140
|
+
* A run with no recorded initiator has nobody to exclude, so this degrades to
|
|
141
|
+
* requiring a signed-in decider.
|
|
142
|
+
*/
|
|
143
|
+
export type WorkflowApprovalApprovers = 'any' | 'owner' | 'not-initiator'
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Who may answer an approval gate. Declared on the gate rather than the
|
|
147
|
+
* entrypoint because it is a property of the decision being made, and because
|
|
148
|
+
* one workflow can hold several gates with different requirements.
|
|
149
|
+
*/
|
|
150
|
+
export interface WorkflowApprovalPolicy {
|
|
151
|
+
/**
|
|
152
|
+
* Defaults to `any`. See {@link WorkflowApprovalApprovers}.
|
|
153
|
+
*/
|
|
154
|
+
approvers?: WorkflowApprovalApprovers
|
|
155
|
+
/**
|
|
156
|
+
* Additionally require the decider's session to hold this scope, so
|
|
157
|
+
* "a second pair of eyes" can be narrowed to "a second pair of *senior*
|
|
158
|
+
* eyes". Combines with `approvers` — both must pass.
|
|
159
|
+
*/
|
|
160
|
+
approverScope?: string
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The decider, reduced to the two facts a policy can be expressed in terms of.
|
|
165
|
+
* Recorded alongside the decision so the gate can be judged on replay — the
|
|
166
|
+
* session itself is long gone by then — and carried into the settled outcome so
|
|
167
|
+
* the answer keeps its provenance.
|
|
168
|
+
*/
|
|
169
|
+
export interface ApprovalDecider {
|
|
170
|
+
userId?: string
|
|
171
|
+
scopes?: string[]
|
|
172
|
+
}
|
|
173
|
+
|
|
117
174
|
/**
|
|
118
175
|
* Options for workflow.approval().
|
|
119
176
|
*/
|
|
120
177
|
export interface WorkflowApprovalOptions<
|
|
121
178
|
TSchema extends StandardSchemaV1 = StandardSchemaV1,
|
|
122
|
-
> {
|
|
179
|
+
> extends WorkflowApprovalPolicy {
|
|
123
180
|
/**
|
|
124
181
|
* Schema the decision payload is validated against. This is a VALUE, not a
|
|
125
182
|
* type generic: the payload arrives from an untrusted caller over the approve
|
|
@@ -142,7 +199,18 @@ export interface WorkflowApprovalOptions<
|
|
|
142
199
|
* carried in `data` and is the application's business, not the framework's.
|
|
143
200
|
*/
|
|
144
201
|
export type ApprovalOutcome<T> =
|
|
145
|
-
| {
|
|
202
|
+
| {
|
|
203
|
+
status: 'decided'
|
|
204
|
+
data: T
|
|
205
|
+
/**
|
|
206
|
+
* Who answered, and when. Run state holds the decision only while the gate
|
|
207
|
+
* is open — it is overwritten by the next write and cleared outright when a
|
|
208
|
+
* decision is refused — so the settled answer carries its own provenance
|
|
209
|
+
* into the step result, which is append-only.
|
|
210
|
+
*/
|
|
211
|
+
decidedBy?: ApprovalDecider
|
|
212
|
+
decidedAt?: string
|
|
213
|
+
}
|
|
146
214
|
| { status: 'expired' }
|
|
147
215
|
|
|
148
216
|
/**
|
|
@@ -416,6 +484,10 @@ export interface ApprovalStepMeta {
|
|
|
416
484
|
outputVar?: string
|
|
417
485
|
/** Expiry duration, when one was given */
|
|
418
486
|
expiry?: string | number
|
|
487
|
+
/** Who may answer the gate, when the default was not used */
|
|
488
|
+
approvers?: WorkflowApprovalApprovers
|
|
489
|
+
/** Scope the decider must hold, when one was required */
|
|
490
|
+
approverScope?: string
|
|
419
491
|
}
|
|
420
492
|
|
|
421
493
|
/**
|
|
@@ -531,15 +603,14 @@ export interface PikkuWorkflowWire {
|
|
|
531
603
|
/**
|
|
532
604
|
* What a scenario has accumulated so far, shared by its body and its hooks.
|
|
533
605
|
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
* object output gets an open record rather than `Partial<never>`.
|
|
606
|
+
* `Partial` because a scenario can fail at any step: `Out` describes what a
|
|
607
|
+
* *completed* run produces, and the context holds however much of it this run
|
|
608
|
+
* reached.
|
|
538
609
|
*
|
|
539
|
-
* `
|
|
540
|
-
*
|
|
541
|
-
* and a constraint carrying a concrete
|
|
542
|
-
* scenario output differs from it.
|
|
610
|
+
* The `0 extends 1 & Out` branch keeps `any` collapsing to `any` rather than to
|
|
611
|
+
* the union of the other two. `PikkuWire` and `PikkuFunctionConfig` both use
|
|
612
|
+
* this type as a generic *constraint*, and a constraint carrying a concrete
|
|
613
|
+
* context would reject every wire whose scenario output differs from it.
|
|
543
614
|
*/
|
|
544
615
|
export type ScenarioContext<Out = unknown> = 0 extends 1 & Out
|
|
545
616
|
? any
|
|
@@ -549,12 +620,9 @@ export type ScenarioContext<Out = unknown> = 0 extends 1 & Out
|
|
|
549
620
|
|
|
550
621
|
export interface PikkuScenarioWire<Out = unknown> extends PikkuWorkflowWire {
|
|
551
622
|
/**
|
|
552
|
-
* Scratch the body writes and the `before`/`after` hooks read.
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
* is why teardown had nowhere to learn the ids the body minted. Assigning
|
|
556
|
-
* them here (`scenario.context.projectId = project.projectId`) hands them to
|
|
557
|
-
* `after`, which runs in a `finally` and so cleans up on a failed run too.
|
|
623
|
+
* Scratch the body writes and the `before`/`after` hooks read. A hook is a
|
|
624
|
+
* separate function and cannot see the body's locals, so this is how teardown
|
|
625
|
+
* learns the ids the body minted.
|
|
558
626
|
*
|
|
559
627
|
* Deliberately *not* a world: it is scoped to one run, and scenario steps
|
|
560
628
|
* cannot reach it. Steps stay pure functions of their declared inputs, so the
|
|
@@ -589,6 +657,18 @@ export interface PikkuScenarioWire<Out = unknown> extends PikkuWorkflowWire {
|
|
|
589
657
|
options?: WorkflowExpectServiceOptions
|
|
590
658
|
) => Promise<void>
|
|
591
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Grade-assertion step: runs one declared scorer against a finished agent run
|
|
662
|
+
* and asserts the score. Returns the grade, so a scenario can report the
|
|
663
|
+
* reason a judge gave rather than only that it fell short.
|
|
664
|
+
*/
|
|
665
|
+
expectScore: (
|
|
666
|
+
stepName: string,
|
|
667
|
+
runId: string,
|
|
668
|
+
scorerName: string,
|
|
669
|
+
options?: WorkflowExpectScoreOptions
|
|
670
|
+
) => Promise<{ score: number; reason?: string }>
|
|
671
|
+
|
|
592
672
|
/**
|
|
593
673
|
* Run a registered scenario step, as the setup the scenario starts from.
|
|
594
674
|
*
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { pikkuState } from '../../pikku-state.js'
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
CoreWorkflow,
|
|
5
|
-
FeaturePlanEntry,
|
|
6
|
-
} from './workflow.types.js'
|
|
2
|
+
import type { CoreWorkflow } from './workflow.types.js'
|
|
3
|
+
import type { CoreFeature, FeaturePlanEntry } from './scenario.types.js'
|
|
7
4
|
|
|
8
5
|
export const addFeature = (
|
|
9
6
|
featureId: string,
|
|
@@ -54,6 +54,78 @@ describe('graph-runner bugs', () => {
|
|
|
54
54
|
assert.equal(run?.status, 'running')
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
+
test('startWorkflow rejects a startNode that is not a declared entry node', async () => {
|
|
58
|
+
const ws = new InMemoryWorkflowService()
|
|
59
|
+
const metaState = pikkuState(null, 'workflows', 'meta')
|
|
60
|
+
metaState['testStartNodeGate'] = {
|
|
61
|
+
name: 'testStartNodeGate',
|
|
62
|
+
pikkuFuncId: 'testStartNodeGate',
|
|
63
|
+
source: 'graph',
|
|
64
|
+
entryNodeIds: ['gate'],
|
|
65
|
+
graphHash: 'startnode-hash',
|
|
66
|
+
nodes: {
|
|
67
|
+
// 'charge' reads only trigger input, so it is dependency-free and would
|
|
68
|
+
// otherwise be directly startable — skipping the 'gate' entry node.
|
|
69
|
+
gate: { nodeId: 'gate', rpcName: 'checkEligibility', next: 'charge' },
|
|
70
|
+
charge: { nodeId: 'charge', rpcName: 'capturePayment' },
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await assert.rejects(
|
|
75
|
+
() =>
|
|
76
|
+
ws.startWorkflow(
|
|
77
|
+
'testStartNodeGate',
|
|
78
|
+
{},
|
|
79
|
+
{ type: 'test' },
|
|
80
|
+
{ rpcWithWire: async () => ({}) },
|
|
81
|
+
{ startNode: 'charge', inline: true }
|
|
82
|
+
),
|
|
83
|
+
/not a declared entry node/
|
|
84
|
+
)
|
|
85
|
+
assert.equal(
|
|
86
|
+
[...(ws as any).runs.keys()].length,
|
|
87
|
+
0,
|
|
88
|
+
'no run should be created for a non-entry startNode'
|
|
89
|
+
)
|
|
90
|
+
delete metaState['testStartNodeGate']
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('startWorkflow accepts a startNode that is a declared entry node', async () => {
|
|
94
|
+
const ws = new InMemoryWorkflowService()
|
|
95
|
+
const metaState = pikkuState(null, 'workflows', 'meta')
|
|
96
|
+
// 'b' is a declared entry but carries an unknown input ref, so it clears the
|
|
97
|
+
// startNode gate and then throws at reference validation — proving the gate
|
|
98
|
+
// admitted it, without dispatching real work.
|
|
99
|
+
metaState['testStartNodeAllowed'] = {
|
|
100
|
+
name: 'testStartNodeAllowed',
|
|
101
|
+
pikkuFuncId: 'testStartNodeAllowed',
|
|
102
|
+
source: 'graph',
|
|
103
|
+
entryNodeIds: ['a', 'b'],
|
|
104
|
+
graphHash: 'startnode-ok-hash',
|
|
105
|
+
nodes: {
|
|
106
|
+
a: { nodeId: 'a', rpcName: 'doA' },
|
|
107
|
+
b: {
|
|
108
|
+
nodeId: 'b',
|
|
109
|
+
rpcName: 'doB',
|
|
110
|
+
input: { dep: { $ref: 'missingNode', path: 'value' } },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
await assert.rejects(
|
|
115
|
+
() =>
|
|
116
|
+
ws.startWorkflow(
|
|
117
|
+
'testStartNodeAllowed',
|
|
118
|
+
{},
|
|
119
|
+
{ type: 'test' },
|
|
120
|
+
{ rpcWithWire: async () => ({}) },
|
|
121
|
+
{ startNode: 'b', inline: true }
|
|
122
|
+
),
|
|
123
|
+
/references unknown node 'missingNode'/,
|
|
124
|
+
'a declared entry node must pass the startNode gate (reaching ref validation)'
|
|
125
|
+
)
|
|
126
|
+
delete metaState['testStartNodeAllowed']
|
|
127
|
+
})
|
|
128
|
+
|
|
57
129
|
test('runWorkflowGraph should throw and not create a run for unknown input refs', async () => {
|
|
58
130
|
const ws = new InMemoryWorkflowService()
|
|
59
131
|
|
|
@@ -13,8 +13,8 @@ export {
|
|
|
13
13
|
assertWorkflowRunOwner,
|
|
14
14
|
WorkflowRunForbiddenError,
|
|
15
15
|
} from './workflow-run-ownership.js'
|
|
16
|
+
export { WorkflowApprovalForbiddenError } from './workflow-approval-policy.js'
|
|
16
17
|
export type {
|
|
17
|
-
RunLifecycleContext,
|
|
18
18
|
WorkflowRunEngine,
|
|
19
19
|
WorkflowRunExtension,
|
|
20
20
|
} from './workflow-run-engine.types.js'
|
|
@@ -28,16 +28,9 @@ export {
|
|
|
28
28
|
reconstructStateAt,
|
|
29
29
|
reconstructFinalState,
|
|
30
30
|
} from './run-timeline.js'
|
|
31
|
-
export type {
|
|
32
|
-
RunTimeline,
|
|
33
|
-
RunTimelineEvent,
|
|
34
|
-
ReconstructedRunState,
|
|
35
|
-
ReconstructedStep,
|
|
36
|
-
RunPhase,
|
|
37
|
-
} from './run-timeline.js'
|
|
31
|
+
export type { RunTimeline, ReconstructedRunState } from './run-timeline.js'
|
|
38
32
|
|
|
39
33
|
export { addWorkflow } from './dsl/workflow-runner.js'
|
|
40
|
-
export { addFeature, resolveFeatureScenarios } from './feature.js'
|
|
41
34
|
|
|
42
35
|
export { template, type TemplateString } from './graph/template.js'
|
|
43
36
|
export {
|
|
@@ -45,12 +38,6 @@ export {
|
|
|
45
38
|
type PikkuWorkflowGraphConfig,
|
|
46
39
|
type PikkuWorkflowGraphResult,
|
|
47
40
|
} from './graph/wire-workflow-graph.js'
|
|
48
|
-
|
|
49
|
-
export {
|
|
50
|
-
pikkuWorkflowWorkerFunc,
|
|
51
|
-
pikkuWorkflowOrchestratorFunc,
|
|
52
|
-
pikkuWorkflowSleeperFunc,
|
|
53
|
-
} from './workflow-queue-workers.js'
|
|
54
41
|
export type {
|
|
55
42
|
WorkflowStepInput as WorkflowStepQueueInput,
|
|
56
43
|
PikkuWorkflowOrchestratorInput,
|
|
@@ -72,35 +59,22 @@ export type {
|
|
|
72
59
|
WorkflowRunService,
|
|
73
60
|
WorkflowRunMirror,
|
|
74
61
|
CoreWorkflow,
|
|
75
|
-
CoreFeature,
|
|
76
|
-
CoreFeatureScenario,
|
|
77
|
-
FeatureMeta,
|
|
78
|
-
FeatureMetaEntry,
|
|
79
|
-
FeaturesMeta,
|
|
80
|
-
FeaturePlanEntry,
|
|
81
62
|
PikkuWorkflow,
|
|
82
63
|
ContextVariable,
|
|
83
64
|
WorkflowContext,
|
|
84
65
|
WorkflowsMeta,
|
|
85
|
-
WorkflowRuntimeMeta,
|
|
86
66
|
WorkflowsRuntimeMeta,
|
|
87
67
|
} from './workflow.types.js'
|
|
88
68
|
|
|
89
69
|
export type {
|
|
90
70
|
WorkflowStepOptions,
|
|
91
71
|
WorkflowWireDoRPC,
|
|
92
|
-
WorkflowWireDoInline,
|
|
93
|
-
WorkflowWireSleep,
|
|
94
|
-
WorkflowWireSuspend,
|
|
95
|
-
WorkflowWireApproval,
|
|
96
72
|
WorkflowApprovalOptions,
|
|
97
73
|
ApprovalOutcome,
|
|
98
74
|
InputSource,
|
|
99
75
|
OutputBinding,
|
|
100
76
|
RpcStepMeta,
|
|
101
|
-
SimpleCondition,
|
|
102
77
|
Condition,
|
|
103
|
-
BranchCase,
|
|
104
78
|
BranchStepMeta,
|
|
105
79
|
ParallelGroupStepMeta,
|
|
106
80
|
FanoutStepMeta,
|
|
@@ -115,47 +89,7 @@ export type {
|
|
|
115
89
|
SwitchStepMeta,
|
|
116
90
|
FilterStepMeta,
|
|
117
91
|
ArrayPredicateStepMeta,
|
|
118
|
-
ScenarioStepInvocation,
|
|
119
|
-
ScenarioStepMeta,
|
|
120
92
|
WorkflowStepMeta,
|
|
121
93
|
WorkflowStepWire,
|
|
122
94
|
PikkuWorkflowWire,
|
|
123
|
-
PikkuScenarioWire,
|
|
124
95
|
} from './workflow.types.js'
|
|
125
|
-
|
|
126
|
-
export type {
|
|
127
|
-
ScenarioStepPhase,
|
|
128
|
-
ScenarioStepKind,
|
|
129
|
-
ScenarioStepOptions,
|
|
130
|
-
PikkuScenarioStepWire,
|
|
131
|
-
ScenarioEnvironment,
|
|
132
|
-
ScenarioSurface,
|
|
133
|
-
ScenarioSurfaceResolution,
|
|
134
|
-
} from './scenario-step.types.js'
|
|
135
|
-
export { SCENARIO_SURFACES } from './scenario-step.types.js'
|
|
136
|
-
|
|
137
|
-
// Which of a step's bindings run: one for an action, every witness for a `then`
|
|
138
|
-
export { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js'
|
|
139
|
-
|
|
140
|
-
export { requireActor, requireScenarioEnv } from './scenario-step-guards.js'
|
|
141
|
-
|
|
142
|
-
export { pollUntil, type PollOptions } from './scenario-poll.js'
|
|
143
|
-
|
|
144
|
-
export { createCookieJar } from './scenario-cookie-jar.js'
|
|
145
|
-
export type { ScenarioCookieJar } from './scenario-cookie-jar.js'
|
|
146
|
-
|
|
147
|
-
export type {
|
|
148
|
-
ScenarioHttpResponse,
|
|
149
|
-
ScenarioJsonRequest,
|
|
150
|
-
} from '../../services/personas-service.js'
|
|
151
|
-
// The readers themselves live on `@pikku/core/persona`; workflow is a production
|
|
152
|
-
// wiring and must not pull scenario runtime in behind it.
|
|
153
|
-
|
|
154
|
-
export type {
|
|
155
|
-
PikkuBrowserWire,
|
|
156
|
-
TestIdSelector,
|
|
157
|
-
ScenarioBrowserProvider,
|
|
158
|
-
ScenarioBrowserFailure,
|
|
159
|
-
} from './scenario-step.types.js'
|
|
160
|
-
|
|
161
|
-
export { composeStepProse, renderStepTemplate } from './scenario-prose.js'
|
|
@@ -27,13 +27,36 @@ import type {
|
|
|
27
27
|
} from './scenario-step.types.js'
|
|
28
28
|
import { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js'
|
|
29
29
|
import type {
|
|
30
|
-
PikkuScenarioWire,
|
|
31
30
|
PikkuWorkflowWire,
|
|
31
|
+
WorkflowQueueOptions,
|
|
32
|
+
} from './workflow.types.js'
|
|
33
|
+
import type { PikkuScenarioWire } from './scenario.types.js'
|
|
34
|
+
import type {
|
|
32
35
|
WorkflowExpectEventuallyOptions,
|
|
33
36
|
WorkflowExpectErrorOptions,
|
|
37
|
+
WorkflowExpectScoreOptions,
|
|
34
38
|
WorkflowExpectServiceOptions,
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
} from './dsl/workflow-dsl.types.js'
|
|
40
|
+
|
|
41
|
+
export { addFeature, resolveFeatureScenarios } from './feature.js'
|
|
42
|
+
export type * from './scenario.types.js'
|
|
43
|
+
export { SCENARIO_SURFACES } from './scenario-step.types.js'
|
|
44
|
+
|
|
45
|
+
// Which of a step's bindings run: one for an action, every witness for a `then`
|
|
46
|
+
export { resolveScenarioSurfaces } from './scenario-surface.js'
|
|
47
|
+
|
|
48
|
+
export { pollUntil, type PollOptions } from './scenario-poll.js'
|
|
49
|
+
export { createCookieJar } from './scenario-cookie-jar.js'
|
|
50
|
+
export type { ScenarioCookieJar } from './scenario-cookie-jar.js'
|
|
51
|
+
export { composeStepProse, renderStepTemplate } from './scenario-prose.js'
|
|
52
|
+
|
|
53
|
+
export { requireActor, requireScenarioEnv } from './scenario-step-guards.js'
|
|
54
|
+
|
|
55
|
+
export type {
|
|
56
|
+
ScenarioHttpResponse,
|
|
57
|
+
ScenarioJsonRequest,
|
|
58
|
+
} from '../../services/personas-service.js'
|
|
59
|
+
// The readers themselves live on `@pikku/core/persona`
|
|
37
60
|
|
|
38
61
|
/**
|
|
39
62
|
* A workflow service with the scenario capability attached — the two lines
|
|
@@ -245,9 +268,9 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
245
268
|
// Scenario actors per run: live authenticated clients (cookie jars) are
|
|
246
269
|
// process-local by nature, so they ride this map, never the persisted wire.
|
|
247
270
|
private runActors = new Map<string, ScenarioPersonas>()
|
|
248
|
-
// What each run has accumulated so far
|
|
249
|
-
//
|
|
250
|
-
//
|
|
271
|
+
// What each run has accumulated so far — process-local like the actors above,
|
|
272
|
+
// so the body and its hooks share one object rather than reading it back off
|
|
273
|
+
// the persisted wire.
|
|
251
274
|
private runContexts = new Map<string, Record<string, unknown>>()
|
|
252
275
|
private scenarioBrowserProvider?: ScenarioBrowserProvider
|
|
253
276
|
private scenarioEnvironment?: ScenarioEnvironment
|
|
@@ -319,19 +342,19 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
319
342
|
}
|
|
320
343
|
|
|
321
344
|
/**
|
|
322
|
-
* What the run has accumulated
|
|
323
|
-
*
|
|
345
|
+
* What the run has accumulated.
|
|
346
|
+
*
|
|
347
|
+
* Undefined only for a run whose wire was never decorated — decoration calls
|
|
348
|
+
* `contextForRun` unconditionally, so a plain workflow that has reached a
|
|
349
|
+
* step holds an empty context rather than none.
|
|
324
350
|
*/
|
|
325
351
|
public getRunContext(runId: string): Record<string, unknown> | undefined {
|
|
326
352
|
return this.runContexts.get(runId)
|
|
327
353
|
}
|
|
328
354
|
|
|
329
355
|
/**
|
|
330
|
-
* The run's context, created on demand
|
|
331
|
-
*
|
|
332
|
-
* `attachRunContext` seeds it for an ordinary scenario run, but the wire is
|
|
333
|
-
* also built on paths that skip attach; without this the body would be
|
|
334
|
-
* writing to an `undefined` and teardown would read nothing.
|
|
356
|
+
* The run's context, created on demand because the wire is also built on
|
|
357
|
+
* paths that skip `attachRunContext`.
|
|
335
358
|
*/
|
|
336
359
|
private contextForRun(runId: string): Record<string, unknown> {
|
|
337
360
|
let runContext = this.runContexts.get(runId)
|
|
@@ -539,9 +562,6 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
539
562
|
rpcService,
|
|
540
563
|
})
|
|
541
564
|
Object.assign(workflowWire, {
|
|
542
|
-
// One object per run, resolved through the map rather than created here,
|
|
543
|
-
// so the body and its before/after hooks share the same scratch even
|
|
544
|
-
// though the hooks are separate functions that cannot see its locals.
|
|
545
565
|
context: this.contextForRun(runId),
|
|
546
566
|
|
|
547
567
|
// Durable polling step: invoke an RPC (as an actor when options.as is
|
|
@@ -684,6 +704,51 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
684
704
|
)
|
|
685
705
|
},
|
|
686
706
|
|
|
707
|
+
expectScore: async (
|
|
708
|
+
stepName: string,
|
|
709
|
+
agentRunId: string,
|
|
710
|
+
scorerName: string,
|
|
711
|
+
options?: WorkflowExpectScoreOptions
|
|
712
|
+
) => {
|
|
713
|
+
this.engine.verifyStepName(stepName)
|
|
714
|
+
return await this.engine.inlineStep(
|
|
715
|
+
runId,
|
|
716
|
+
stepName,
|
|
717
|
+
async () => {
|
|
718
|
+
const rpcName = 'pikkuScenarioGradeRun'
|
|
719
|
+
const data = {
|
|
720
|
+
runId: agentRunId,
|
|
721
|
+
scorer: scorerName,
|
|
722
|
+
...(options?.reference !== undefined
|
|
723
|
+
? { reference: options.reference }
|
|
724
|
+
: {}),
|
|
725
|
+
}
|
|
726
|
+
const grade: { score: number; reason?: string } = options?.actor
|
|
727
|
+
? await options.actor.invoke(rpcName, data)
|
|
728
|
+
: await rpcService.rpcWithWire(rpcName, data, {})
|
|
729
|
+
|
|
730
|
+
// A scorer that graded is a scorer that answered, so an unstated
|
|
731
|
+
// bound still fails a zero rather than passing anything at all.
|
|
732
|
+
const atLeast = options?.atLeast ?? 0.5
|
|
733
|
+
const failed =
|
|
734
|
+
grade.score < atLeast ||
|
|
735
|
+
(options?.atMost !== undefined && grade.score > options.atMost)
|
|
736
|
+
if (failed) {
|
|
737
|
+
const bound =
|
|
738
|
+
options?.atMost !== undefined
|
|
739
|
+
? `between ${atLeast} and ${options.atMost}`
|
|
740
|
+
: `at least ${atLeast}`
|
|
741
|
+
throw new Error(
|
|
742
|
+
`[workflow] expectScore '${stepName}' expected '${scorerName}' to grade run ${agentRunId} ${bound}, got ${grade.score}` +
|
|
743
|
+
`${grade.reason ? `: ${grade.reason}` : ''}`
|
|
744
|
+
)
|
|
745
|
+
}
|
|
746
|
+
return grade
|
|
747
|
+
},
|
|
748
|
+
options
|
|
749
|
+
)
|
|
750
|
+
},
|
|
751
|
+
|
|
687
752
|
// knowledge: decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md
|
|
688
753
|
given: (stepName, stepFunc, data, options) =>
|
|
689
754
|
this.scenarioStep(
|
|
@@ -661,12 +661,9 @@ describe('pikku-workflow-service approval', () => {
|
|
|
661
661
|
|
|
662
662
|
const run = await ws.getRun(runId)
|
|
663
663
|
assert.equal(run?.status, 'completed')
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
data: { approved: true, comment: 'lgtm' },
|
|
668
|
-
},
|
|
669
|
-
})
|
|
664
|
+
const decision = (run?.output as any)?.decision
|
|
665
|
+
assert.equal(decision.status, 'decided')
|
|
666
|
+
assert.deepEqual(decision.data, { approved: true, comment: 'lgtm' })
|
|
670
667
|
|
|
671
668
|
cleanup()
|
|
672
669
|
})
|
|
@@ -714,9 +711,9 @@ describe('pikku-workflow-service approval', () => {
|
|
|
714
711
|
await ws.runWorkflowJob(runId, {})
|
|
715
712
|
const run = await ws.getRun(runId)
|
|
716
713
|
assert.equal(run?.status, 'completed')
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
})
|
|
714
|
+
const decision = (run?.output as any)?.decision
|
|
715
|
+
assert.equal(decision.status, 'decided')
|
|
716
|
+
assert.deepEqual(decision.data, { approved: false })
|
|
720
717
|
|
|
721
718
|
cleanup()
|
|
722
719
|
})
|
|
@@ -834,9 +831,13 @@ describe('pikku-workflow-service approval', () => {
|
|
|
834
831
|
|
|
835
832
|
const run = await ws.getRun(runId)
|
|
836
833
|
assert.equal(run?.status, 'completed')
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
})
|
|
834
|
+
const decision = (run?.output as any)?.decision
|
|
835
|
+
assert.equal(decision.status, 'decided')
|
|
836
|
+
assert.deepEqual(decision.data, { approved: true })
|
|
837
|
+
// Answered with no session, so there is nobody to name — but when the
|
|
838
|
+
// answer arrived is recorded either way.
|
|
839
|
+
assert.equal(decision.decidedBy, undefined)
|
|
840
|
+
assert.ok(!Number.isNaN(Date.parse(decision.decidedAt)))
|
|
840
841
|
|
|
841
842
|
cleanup()
|
|
842
843
|
})
|
|
@@ -47,7 +47,7 @@ import { PikkuMissingMetaError } from '../../errors/errors.js'
|
|
|
47
47
|
import { RPCNotFoundError } from '../rpc/rpc-runner.js'
|
|
48
48
|
import type { PikkuRPC } from '../rpc/rpc-types.js'
|
|
49
49
|
import { deriveInvocationId } from './workflow-invocation-id.js'
|
|
50
|
-
import {
|
|
50
|
+
import { approvalDeciderFrom } from './workflow-approval-policy.js'
|
|
51
51
|
import {
|
|
52
52
|
buildRunTimeline,
|
|
53
53
|
reconstructStateAt,
|
|
@@ -95,6 +95,7 @@ import {
|
|
|
95
95
|
recordApprovalDecision,
|
|
96
96
|
type ApprovalStore,
|
|
97
97
|
} from './workflow-approval.js'
|
|
98
|
+
import { auditApprovalDecision } from './workflow-approval-audit.js'
|
|
98
99
|
import { recordSuspension, suspendStepNameFor } from './workflow-suspend.js'
|
|
99
100
|
import {
|
|
100
101
|
RedispatchBackoff,
|
|
@@ -844,6 +845,22 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
844
845
|
}
|
|
845
846
|
|
|
846
847
|
if (workflowMeta.source === 'graph') {
|
|
848
|
+
// A caller-supplied startNode must be one of the graph's declared entry
|
|
849
|
+
// nodes. startWorkflow is the boundary the public
|
|
850
|
+
// `/workflow/:name/graph/:nodeId` route and triggers enter through, so
|
|
851
|
+
// without this a request could name any dependency-free node — one whose
|
|
852
|
+
// input reads only `trigger` — and fire its RPC directly with
|
|
853
|
+
// attacker-chosen data, skipping every upstream eligibility, validation or
|
|
854
|
+
// approval node. (Internal resume/replay drives runWorkflowGraph directly
|
|
855
|
+
// and is unaffected.)
|
|
856
|
+
if (
|
|
857
|
+
options?.startNode &&
|
|
858
|
+
!(workflowMeta.entryNodeIds ?? []).includes(options.startNode)
|
|
859
|
+
) {
|
|
860
|
+
throw new Error(
|
|
861
|
+
`Workflow graph '${name}': '${options.startNode}' is not a declared entry node`
|
|
862
|
+
)
|
|
863
|
+
}
|
|
847
864
|
const shouldInline =
|
|
848
865
|
options?.inline || !getSingletonServices()?.queueService
|
|
849
866
|
return runWorkflowGraph(
|
|
@@ -1840,6 +1857,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1840
1857
|
this.updateRunState(runId, key, value),
|
|
1841
1858
|
resumeWorkflow: (runId) => this.resumeWorkflow(runId),
|
|
1842
1859
|
scheduleRunWake: (runId, delay) => this.scheduleRunWake(runId, delay),
|
|
1860
|
+
getRunOwner: async (runId) =>
|
|
1861
|
+
(await this.getRunIdentity(runId))?.wire?.pikkuUserId,
|
|
1862
|
+
auditApproval: (event) => auditApprovalDecision(event),
|
|
1843
1863
|
}
|
|
1844
1864
|
}
|
|
1845
1865
|
|
|
@@ -1849,9 +1869,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1849
1869
|
decision: unknown,
|
|
1850
1870
|
session?: CoreUserSession
|
|
1851
1871
|
): Promise<void> {
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1872
|
+
return recordApprovalDecision(
|
|
1873
|
+
this.approvalStore,
|
|
1874
|
+
runId,
|
|
1875
|
+
reason,
|
|
1876
|
+
decision,
|
|
1877
|
+
approvalDeciderFrom(session)
|
|
1878
|
+
)
|
|
1855
1879
|
}
|
|
1856
1880
|
|
|
1857
1881
|
private async approvalStep(
|