@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
|
@@ -53,6 +53,19 @@ export interface WorkflowExpectServiceOptions extends WorkflowStepOptions {
|
|
|
53
53
|
/** Assert the exact number of matching calls. Default: at least one. */
|
|
54
54
|
times?: number;
|
|
55
55
|
}
|
|
56
|
+
/** Options for workflow.expectScore() */
|
|
57
|
+
export interface WorkflowExpectScoreOptions extends WorkflowStepOptions {
|
|
58
|
+
/** Fail below this score. Defaults to 0.5 — a scorer that answered at all. */
|
|
59
|
+
atLeast?: number;
|
|
60
|
+
/** Fail above this score, for a scorer where high is the failure. */
|
|
61
|
+
atMost?: number;
|
|
62
|
+
/**
|
|
63
|
+
* The known-correct answer, which is what a reference-based judge grades
|
|
64
|
+
* against. Live traffic has no answer key, so a judge declared
|
|
65
|
+
* `requiresReference` is only ever reachable from a scenario.
|
|
66
|
+
*/
|
|
67
|
+
reference?: string;
|
|
68
|
+
}
|
|
56
69
|
/**
|
|
57
70
|
* Type signature for workflow.do() RPC form - used by inspector
|
|
58
71
|
*/
|
|
@@ -80,10 +93,50 @@ export type WorkflowWireSleep = (stepName: string, duration: string) => Promise<
|
|
|
80
93
|
* loops, like dynamic `do()` step names.
|
|
81
94
|
*/
|
|
82
95
|
export type WorkflowWireSuspend = (reason: string) => Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Who is allowed to answer an approval gate, relative to the user who started
|
|
98
|
+
* the run.
|
|
99
|
+
*
|
|
100
|
+
* - `any` — anyone the approve entrypoint lets through. The gate is a pause for
|
|
101
|
+
* a decision, not an authorization boundary.
|
|
102
|
+
* - `owner` — only the user who started the run, so a gate can be used to
|
|
103
|
+
* confirm an action against the same person who requested it.
|
|
104
|
+
* - `not-initiator` — anyone *except* the user who started the run: four-eyes.
|
|
105
|
+
* A run with no recorded initiator has nobody to exclude, so this degrades to
|
|
106
|
+
* requiring a signed-in decider.
|
|
107
|
+
*/
|
|
108
|
+
export type WorkflowApprovalApprovers = 'any' | 'owner' | 'not-initiator';
|
|
109
|
+
/**
|
|
110
|
+
* Who may answer an approval gate. Declared on the gate rather than the
|
|
111
|
+
* entrypoint because it is a property of the decision being made, and because
|
|
112
|
+
* one workflow can hold several gates with different requirements.
|
|
113
|
+
*/
|
|
114
|
+
export interface WorkflowApprovalPolicy {
|
|
115
|
+
/**
|
|
116
|
+
* Defaults to `any`. See {@link WorkflowApprovalApprovers}.
|
|
117
|
+
*/
|
|
118
|
+
approvers?: WorkflowApprovalApprovers;
|
|
119
|
+
/**
|
|
120
|
+
* Additionally require the decider's session to hold this scope, so
|
|
121
|
+
* "a second pair of eyes" can be narrowed to "a second pair of *senior*
|
|
122
|
+
* eyes". Combines with `approvers` — both must pass.
|
|
123
|
+
*/
|
|
124
|
+
approverScope?: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The decider, reduced to the two facts a policy can be expressed in terms of.
|
|
128
|
+
* Recorded alongside the decision so the gate can be judged on replay — the
|
|
129
|
+
* session itself is long gone by then — and carried into the settled outcome so
|
|
130
|
+
* the answer keeps its provenance.
|
|
131
|
+
*/
|
|
132
|
+
export interface ApprovalDecider {
|
|
133
|
+
userId?: string;
|
|
134
|
+
scopes?: string[];
|
|
135
|
+
}
|
|
83
136
|
/**
|
|
84
137
|
* Options for workflow.approval().
|
|
85
138
|
*/
|
|
86
|
-
export interface WorkflowApprovalOptions<TSchema extends StandardSchemaV1 = StandardSchemaV1> {
|
|
139
|
+
export interface WorkflowApprovalOptions<TSchema extends StandardSchemaV1 = StandardSchemaV1> extends WorkflowApprovalPolicy {
|
|
87
140
|
/**
|
|
88
141
|
* Schema the decision payload is validated against. This is a VALUE, not a
|
|
89
142
|
* type generic: the payload arrives from an untrusted caller over the approve
|
|
@@ -107,6 +160,14 @@ export interface WorkflowApprovalOptions<TSchema extends StandardSchemaV1 = Stan
|
|
|
107
160
|
export type ApprovalOutcome<T> = {
|
|
108
161
|
status: 'decided';
|
|
109
162
|
data: T;
|
|
163
|
+
/**
|
|
164
|
+
* Who answered, and when. Run state holds the decision only while the gate
|
|
165
|
+
* is open — it is overwritten by the next write and cleared outright when a
|
|
166
|
+
* decision is refused — so the settled answer carries its own provenance
|
|
167
|
+
* into the step result, which is append-only.
|
|
168
|
+
*/
|
|
169
|
+
decidedBy?: ApprovalDecider;
|
|
170
|
+
decidedAt?: string;
|
|
110
171
|
} | {
|
|
111
172
|
status: 'expired';
|
|
112
173
|
};
|
|
@@ -386,6 +447,10 @@ export interface ApprovalStepMeta {
|
|
|
386
447
|
outputVar?: string;
|
|
387
448
|
/** Expiry duration, when one was given */
|
|
388
449
|
expiry?: string | number;
|
|
450
|
+
/** Who may answer the gate, when the default was not used */
|
|
451
|
+
approvers?: WorkflowApprovalApprovers;
|
|
452
|
+
/** Scope the decider must hold, when one was required */
|
|
453
|
+
approverScope?: string;
|
|
389
454
|
}
|
|
390
455
|
/**
|
|
391
456
|
* Filter step metadata (array.filter)
|
|
@@ -476,25 +541,21 @@ export interface PikkuWorkflowWire {
|
|
|
476
541
|
/**
|
|
477
542
|
* What a scenario has accumulated so far, shared by its body and its hooks.
|
|
478
543
|
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
* object output gets an open record rather than `Partial<never>`.
|
|
544
|
+
* `Partial` because a scenario can fail at any step: `Out` describes what a
|
|
545
|
+
* *completed* run produces, and the context holds however much of it this run
|
|
546
|
+
* reached.
|
|
483
547
|
*
|
|
484
|
-
* `
|
|
485
|
-
*
|
|
486
|
-
* and a constraint carrying a concrete
|
|
487
|
-
* scenario output differs from it.
|
|
548
|
+
* The `0 extends 1 & Out` branch keeps `any` collapsing to `any` rather than to
|
|
549
|
+
* the union of the other two. `PikkuWire` and `PikkuFunctionConfig` both use
|
|
550
|
+
* this type as a generic *constraint*, and a constraint carrying a concrete
|
|
551
|
+
* context would reject every wire whose scenario output differs from it.
|
|
488
552
|
*/
|
|
489
553
|
export type ScenarioContext<Out = unknown> = 0 extends 1 & Out ? any : Out extends object ? Partial<Out> : Record<string, unknown>;
|
|
490
554
|
export interface PikkuScenarioWire<Out = unknown> extends PikkuWorkflowWire {
|
|
491
555
|
/**
|
|
492
|
-
* Scratch the body writes and the `before`/`after` hooks read.
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
* is why teardown had nowhere to learn the ids the body minted. Assigning
|
|
496
|
-
* them here (`scenario.context.projectId = project.projectId`) hands them to
|
|
497
|
-
* `after`, which runs in a `finally` and so cleans up on a failed run too.
|
|
556
|
+
* Scratch the body writes and the `before`/`after` hooks read. A hook is a
|
|
557
|
+
* separate function and cannot see the body's locals, so this is how teardown
|
|
558
|
+
* learns the ids the body minted.
|
|
498
559
|
*
|
|
499
560
|
* Deliberately *not* a world: it is scoped to one run, and scenario steps
|
|
500
561
|
* cannot reach it. Steps stay pure functions of their declared inputs, so the
|
|
@@ -510,6 +571,15 @@ export interface PikkuScenarioWire<Out = unknown> extends PikkuWorkflowWire {
|
|
|
510
571
|
expectError: <TInput = any>(stepName: string, rpcName: string, data: TInput, options?: WorkflowExpectErrorOptions) => Promise<string>;
|
|
511
572
|
/** Stub-assertion step: asserts `service.method` was called on the target server */
|
|
512
573
|
expectService: (stepName: string, serviceMethod: string, options?: WorkflowExpectServiceOptions) => Promise<void>;
|
|
574
|
+
/**
|
|
575
|
+
* Grade-assertion step: runs one declared scorer against a finished agent run
|
|
576
|
+
* and asserts the score. Returns the grade, so a scenario can report the
|
|
577
|
+
* reason a judge gave rather than only that it fell short.
|
|
578
|
+
*/
|
|
579
|
+
expectScore: (stepName: string, runId: string, scorerName: string, options?: WorkflowExpectScoreOptions) => Promise<{
|
|
580
|
+
score: number;
|
|
581
|
+
reason?: string;
|
|
582
|
+
}>;
|
|
513
583
|
/**
|
|
514
584
|
* Run a registered scenario step, as the setup the scenario starts from.
|
|
515
585
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CoreWorkflow } from './workflow.types.js';
|
|
2
|
+
import type { CoreFeature, FeaturePlanEntry } from './scenario.types.js';
|
|
2
3
|
export declare const addFeature: (featureId: string, feature: CoreFeature, packageName?: string | null) => void;
|
|
3
4
|
export declare const resolveFeatureScenarios: (features: Map<string, CoreFeature>, registrations: Map<string, CoreWorkflow>) => {
|
|
4
5
|
entries: FeaturePlanEntry[];
|
|
@@ -2,27 +2,16 @@ export { PikkuWorkflowService } from './pikku-workflow-service.js';
|
|
|
2
2
|
export { WorkflowCancelledException, WorkflowSuspendedException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowApprovalResolvedError, WorkflowStepFunctionMismatchError, } from './workflow-errors.js';
|
|
3
3
|
export { DEFAULT_STEP_RETRIES } from './workflow-constants.js';
|
|
4
4
|
export { assertWorkflowRunOwner, WorkflowRunForbiddenError, } from './workflow-run-ownership.js';
|
|
5
|
-
export
|
|
5
|
+
export { WorkflowApprovalForbiddenError } from './workflow-approval-policy.js';
|
|
6
|
+
export type { WorkflowRunEngine, WorkflowRunExtension, } from './workflow-run-engine.types.js';
|
|
6
7
|
export { deriveInvocationId, uuidv5 } from './workflow-invocation-id.js';
|
|
7
8
|
export { isRef } from './graph/workflow-graph.types.js';
|
|
8
9
|
export type { RefValue } from './graph/workflow-graph.types.js';
|
|
9
10
|
export { buildRunTimeline, reconstructStateAt, reconstructFinalState, } from './run-timeline.js';
|
|
10
|
-
export type { RunTimeline,
|
|
11
|
+
export type { RunTimeline, ReconstructedRunState } from './run-timeline.js';
|
|
11
12
|
export { addWorkflow } from './dsl/workflow-runner.js';
|
|
12
|
-
export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
13
13
|
export { template, type TemplateString } from './graph/template.js';
|
|
14
14
|
export { pikkuWorkflowGraph, type PikkuWorkflowGraphConfig, type PikkuWorkflowGraphResult, } from './graph/wire-workflow-graph.js';
|
|
15
|
-
export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
|
|
16
15
|
export type { WorkflowStepInput as WorkflowStepQueueInput, PikkuWorkflowOrchestratorInput, PikkuWorkflowSleeperInput, } from './workflow-queue-workers.js';
|
|
17
|
-
export type { WorkflowService, WorkflowQueueOptions, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow,
|
|
18
|
-
export type { WorkflowStepOptions, WorkflowWireDoRPC,
|
|
19
|
-
export type { ScenarioStepPhase, ScenarioStepKind, ScenarioStepOptions, PikkuScenarioStepWire, ScenarioEnvironment, ScenarioSurface, ScenarioSurfaceResolution, } from './scenario-step.types.js';
|
|
20
|
-
export { SCENARIO_SURFACES } from './scenario-step.types.js';
|
|
21
|
-
export { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js';
|
|
22
|
-
export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
|
|
23
|
-
export { pollUntil, type PollOptions } from './scenario-poll.js';
|
|
24
|
-
export { createCookieJar } from './scenario-cookie-jar.js';
|
|
25
|
-
export type { ScenarioCookieJar } from './scenario-cookie-jar.js';
|
|
26
|
-
export type { ScenarioHttpResponse, ScenarioJsonRequest, } from '../../services/personas-service.js';
|
|
27
|
-
export type { PikkuBrowserWire, TestIdSelector, ScenarioBrowserProvider, ScenarioBrowserFailure, } from './scenario-step.types.js';
|
|
28
|
-
export { composeStepProse, renderStepTemplate } from './scenario-prose.js';
|
|
16
|
+
export type { WorkflowService, WorkflowQueueOptions, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowsRuntimeMeta, } from './workflow.types.js';
|
|
17
|
+
export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, Condition, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, } from './workflow.types.js';
|
|
@@ -2,18 +2,10 @@ export { PikkuWorkflowService } from './pikku-workflow-service.js';
|
|
|
2
2
|
export { WorkflowCancelledException, WorkflowSuspendedException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowApprovalResolvedError, WorkflowStepFunctionMismatchError, } from './workflow-errors.js';
|
|
3
3
|
export { DEFAULT_STEP_RETRIES } from './workflow-constants.js';
|
|
4
4
|
export { assertWorkflowRunOwner, WorkflowRunForbiddenError, } from './workflow-run-ownership.js';
|
|
5
|
+
export { WorkflowApprovalForbiddenError } from './workflow-approval-policy.js';
|
|
5
6
|
export { deriveInvocationId, uuidv5 } from './workflow-invocation-id.js';
|
|
6
7
|
export { isRef } from './graph/workflow-graph.types.js';
|
|
7
8
|
export { buildRunTimeline, reconstructStateAt, reconstructFinalState, } from './run-timeline.js';
|
|
8
9
|
export { addWorkflow } from './dsl/workflow-runner.js';
|
|
9
|
-
export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
10
10
|
export { template } from './graph/template.js';
|
|
11
11
|
export { pikkuWorkflowGraph, } from './graph/wire-workflow-graph.js';
|
|
12
|
-
export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
|
|
13
|
-
export { SCENARIO_SURFACES } from './scenario-step.types.js';
|
|
14
|
-
// Which of a step's bindings run: one for an action, every witness for a `then`
|
|
15
|
-
export { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js';
|
|
16
|
-
export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
|
|
17
|
-
export { pollUntil } from './scenario-poll.js';
|
|
18
|
-
export { createCookieJar } from './scenario-cookie-jar.js';
|
|
19
|
-
export { composeStepProse, renderStepTemplate } from './scenario-prose.js';
|
|
@@ -5,6 +5,16 @@ import type { PikkuRawWire } from '../../types/core.types.js';
|
|
|
5
5
|
import type { ScenarioPersonas } from '../../services/personas-service.js';
|
|
6
6
|
import type { ScenarioBrowserProvider, ScenarioEnvironment, ScenarioSurface } from './scenario-step.types.js';
|
|
7
7
|
import type { PikkuWorkflowWire, WorkflowQueueOptions } from './workflow.types.js';
|
|
8
|
+
export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
9
|
+
export type * from './scenario.types.js';
|
|
10
|
+
export { SCENARIO_SURFACES } from './scenario-step.types.js';
|
|
11
|
+
export { resolveScenarioSurfaces } from './scenario-surface.js';
|
|
12
|
+
export { pollUntil, type PollOptions } from './scenario-poll.js';
|
|
13
|
+
export { createCookieJar } from './scenario-cookie-jar.js';
|
|
14
|
+
export type { ScenarioCookieJar } from './scenario-cookie-jar.js';
|
|
15
|
+
export { composeStepProse, renderStepTemplate } from './scenario-prose.js';
|
|
16
|
+
export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
|
|
17
|
+
export type { ScenarioHttpResponse, ScenarioJsonRequest, } from '../../services/personas-service.js';
|
|
8
18
|
/**
|
|
9
19
|
* A workflow service with the scenario capability attached — the two lines
|
|
10
20
|
* `pikku scenario run` needs, in one call so no caller has to remember that the
|
|
@@ -156,16 +166,16 @@ export declare class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
156
166
|
}): Promise<void>;
|
|
157
167
|
detachRunContext(runId: string): void;
|
|
158
168
|
/**
|
|
159
|
-
* What the run has accumulated
|
|
160
|
-
*
|
|
169
|
+
* What the run has accumulated.
|
|
170
|
+
*
|
|
171
|
+
* Undefined only for a run whose wire was never decorated — decoration calls
|
|
172
|
+
* `contextForRun` unconditionally, so a plain workflow that has reached a
|
|
173
|
+
* step holds an empty context rather than none.
|
|
161
174
|
*/
|
|
162
175
|
getRunContext(runId: string): Record<string, unknown> | undefined;
|
|
163
176
|
/**
|
|
164
|
-
* The run's context, created on demand
|
|
165
|
-
*
|
|
166
|
-
* `attachRunContext` seeds it for an ordinary scenario run, but the wire is
|
|
167
|
-
* also built on paths that skip attach; without this the body would be
|
|
168
|
-
* writing to an `undefined` and teardown would read nothing.
|
|
177
|
+
* The run's context, created on demand because the wire is also built on
|
|
178
|
+
* paths that skip `attachRunContext`.
|
|
169
179
|
*/
|
|
170
180
|
private contextForRun;
|
|
171
181
|
decorateRunWire(wire: PikkuRawWire, context: {
|
|
@@ -7,6 +7,15 @@ import { InMemoryWorkflowService } from '../../services/in-memory-workflow-servi
|
|
|
7
7
|
import { runScheduledTask } from '../scheduler/scheduler-runner.js';
|
|
8
8
|
import { WorkflowStepNameNotString } from './workflow-errors.js';
|
|
9
9
|
import { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js';
|
|
10
|
+
export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
11
|
+
export { SCENARIO_SURFACES } from './scenario-step.types.js';
|
|
12
|
+
// Which of a step's bindings run: one for an action, every witness for a `then`
|
|
13
|
+
export { resolveScenarioSurfaces } from './scenario-surface.js';
|
|
14
|
+
export { pollUntil } from './scenario-poll.js';
|
|
15
|
+
export { createCookieJar } from './scenario-cookie-jar.js';
|
|
16
|
+
export { composeStepProse, renderStepTemplate } from './scenario-prose.js';
|
|
17
|
+
export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
|
|
18
|
+
// The readers themselves live on `@pikku/core/persona`
|
|
10
19
|
/**
|
|
11
20
|
* A workflow service with the scenario capability attached — the two lines
|
|
12
21
|
* `pikku scenario run` needs, in one call so no caller has to remember that the
|
|
@@ -176,9 +185,9 @@ export class PikkuScenarioService {
|
|
|
176
185
|
// Scenario actors per run: live authenticated clients (cookie jars) are
|
|
177
186
|
// process-local by nature, so they ride this map, never the persisted wire.
|
|
178
187
|
runActors = new Map();
|
|
179
|
-
// What each run has accumulated so far
|
|
180
|
-
//
|
|
181
|
-
//
|
|
188
|
+
// What each run has accumulated so far — process-local like the actors above,
|
|
189
|
+
// so the body and its hooks share one object rather than reading it back off
|
|
190
|
+
// the persisted wire.
|
|
182
191
|
runContexts = new Map();
|
|
183
192
|
scenarioBrowserProvider;
|
|
184
193
|
scenarioEnvironment;
|
|
@@ -235,18 +244,18 @@ export class PikkuScenarioService {
|
|
|
235
244
|
this.runContexts.delete(runId);
|
|
236
245
|
}
|
|
237
246
|
/**
|
|
238
|
-
* What the run has accumulated
|
|
239
|
-
*
|
|
247
|
+
* What the run has accumulated.
|
|
248
|
+
*
|
|
249
|
+
* Undefined only for a run whose wire was never decorated — decoration calls
|
|
250
|
+
* `contextForRun` unconditionally, so a plain workflow that has reached a
|
|
251
|
+
* step holds an empty context rather than none.
|
|
240
252
|
*/
|
|
241
253
|
getRunContext(runId) {
|
|
242
254
|
return this.runContexts.get(runId);
|
|
243
255
|
}
|
|
244
256
|
/**
|
|
245
|
-
* The run's context, created on demand
|
|
246
|
-
*
|
|
247
|
-
* `attachRunContext` seeds it for an ordinary scenario run, but the wire is
|
|
248
|
-
* also built on paths that skip attach; without this the body would be
|
|
249
|
-
* writing to an `undefined` and teardown would read nothing.
|
|
257
|
+
* The run's context, created on demand because the wire is also built on
|
|
258
|
+
* paths that skip `attachRunContext`.
|
|
250
259
|
*/
|
|
251
260
|
contextForRun(runId) {
|
|
252
261
|
let runContext = this.runContexts.get(runId);
|
|
@@ -397,9 +406,6 @@ export class PikkuScenarioService {
|
|
|
397
406
|
rpcService,
|
|
398
407
|
});
|
|
399
408
|
Object.assign(workflowWire, {
|
|
400
|
-
// One object per run, resolved through the map rather than created here,
|
|
401
|
-
// so the body and its before/after hooks share the same scratch even
|
|
402
|
-
// though the hooks are separate functions that cannot see its locals.
|
|
403
409
|
context: this.contextForRun(runId),
|
|
404
410
|
// Durable polling step: invoke an RPC (as an actor when options.as is
|
|
405
411
|
// set) until the predicate passes or `within` elapses. The whole poll is
|
|
@@ -485,6 +491,35 @@ export class PikkuScenarioService {
|
|
|
485
491
|
}
|
|
486
492
|
}, options);
|
|
487
493
|
},
|
|
494
|
+
expectScore: async (stepName, agentRunId, scorerName, options) => {
|
|
495
|
+
this.engine.verifyStepName(stepName);
|
|
496
|
+
return await this.engine.inlineStep(runId, stepName, async () => {
|
|
497
|
+
const rpcName = 'pikkuScenarioGradeRun';
|
|
498
|
+
const data = {
|
|
499
|
+
runId: agentRunId,
|
|
500
|
+
scorer: scorerName,
|
|
501
|
+
...(options?.reference !== undefined
|
|
502
|
+
? { reference: options.reference }
|
|
503
|
+
: {}),
|
|
504
|
+
};
|
|
505
|
+
const grade = options?.actor
|
|
506
|
+
? await options.actor.invoke(rpcName, data)
|
|
507
|
+
: await rpcService.rpcWithWire(rpcName, data, {});
|
|
508
|
+
// A scorer that graded is a scorer that answered, so an unstated
|
|
509
|
+
// bound still fails a zero rather than passing anything at all.
|
|
510
|
+
const atLeast = options?.atLeast ?? 0.5;
|
|
511
|
+
const failed = grade.score < atLeast ||
|
|
512
|
+
(options?.atMost !== undefined && grade.score > options.atMost);
|
|
513
|
+
if (failed) {
|
|
514
|
+
const bound = options?.atMost !== undefined
|
|
515
|
+
? `between ${atLeast} and ${options.atMost}`
|
|
516
|
+
: `at least ${atLeast}`;
|
|
517
|
+
throw new Error(`[workflow] expectScore '${stepName}' expected '${scorerName}' to grade run ${agentRunId} ${bound}, got ${grade.score}` +
|
|
518
|
+
`${grade.reason ? `: ${grade.reason}` : ''}`);
|
|
519
|
+
}
|
|
520
|
+
return grade;
|
|
521
|
+
}, options);
|
|
522
|
+
},
|
|
488
523
|
// knowledge: decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md
|
|
489
524
|
given: (stepName, stepFunc, data, options) => this.scenarioStep('given', scenarioStepContext(), stepName, stepFunc, data, options),
|
|
490
525
|
when: (stepName, stepFunc, data, options) => this.scenarioStep('when', scenarioStepContext(), stepName, stepFunc, data, options),
|
|
@@ -6,7 +6,7 @@ import { isExpectedError } from '../../errors/error-handler.js';
|
|
|
6
6
|
import { PikkuMissingMetaError } from '../../errors/errors.js';
|
|
7
7
|
import { RPCNotFoundError } from '../rpc/rpc-runner.js';
|
|
8
8
|
import { deriveInvocationId } from './workflow-invocation-id.js';
|
|
9
|
-
import {
|
|
9
|
+
import { approvalDeciderFrom } from './workflow-approval-policy.js';
|
|
10
10
|
import { buildRunTimeline, reconstructStateAt, } from './run-timeline.js';
|
|
11
11
|
import { DEFAULT_STEP_RETRIES, WORKFLOW_CHILD_POLL_MAX_MS, WORKFLOW_END_STATES, WORKFLOW_POLL_FACTOR, WORKFLOW_POLL_MIN_MS, WORKFLOW_TERMINAL_STATES, } from './workflow-constants.js';
|
|
12
12
|
import { WorkflowAsyncException, WorkflowCancelledException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotFoundError, WorkflowStepFunctionMismatchError, WorkflowStepNameNotString, WorkflowSuspendedException, } from './workflow-errors.js';
|
|
@@ -14,6 +14,7 @@ import { resolveWorkflowMeta } from './workflow-meta-resolver.js';
|
|
|
14
14
|
import { jobGroupFor, orchestratorQueueName, resolveWorkflowConfig, stepJobOptions, stepWorkerQueueName, } from './workflow-queue-routing.js';
|
|
15
15
|
import { wireWorkflowQueueWorkers } from './workflow-queue-wiring.js';
|
|
16
16
|
import { approvalStepNameFor, evaluateApprovalStep, recordApprovalDecision, } from './workflow-approval.js';
|
|
17
|
+
import { auditApprovalDecision } from './workflow-approval-audit.js';
|
|
17
18
|
import { recordSuspension, suspendStepNameFor } from './workflow-suspend.js';
|
|
18
19
|
import { RedispatchBackoff, sweepStalledRuns, sweepUndispatchedSteps, } from './workflow-recovery.js';
|
|
19
20
|
export class PikkuWorkflowService {
|
|
@@ -371,6 +372,18 @@ export class PikkuWorkflowService {
|
|
|
371
372
|
throw new WorkflowNotFoundError(name);
|
|
372
373
|
}
|
|
373
374
|
if (workflowMeta.source === 'graph') {
|
|
375
|
+
// A caller-supplied startNode must be one of the graph's declared entry
|
|
376
|
+
// nodes. startWorkflow is the boundary the public
|
|
377
|
+
// `/workflow/:name/graph/:nodeId` route and triggers enter through, so
|
|
378
|
+
// without this a request could name any dependency-free node — one whose
|
|
379
|
+
// input reads only `trigger` — and fire its RPC directly with
|
|
380
|
+
// attacker-chosen data, skipping every upstream eligibility, validation or
|
|
381
|
+
// approval node. (Internal resume/replay drives runWorkflowGraph directly
|
|
382
|
+
// and is unaffected.)
|
|
383
|
+
if (options?.startNode &&
|
|
384
|
+
!(workflowMeta.entryNodeIds ?? []).includes(options.startNode)) {
|
|
385
|
+
throw new Error(`Workflow graph '${name}': '${options.startNode}' is not a declared entry node`);
|
|
386
|
+
}
|
|
374
387
|
const shouldInline = options?.inline || !getSingletonServices()?.queueService;
|
|
375
388
|
return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire, workflowMeta);
|
|
376
389
|
}
|
|
@@ -1014,11 +1027,12 @@ export class PikkuWorkflowService {
|
|
|
1014
1027
|
updateRunState: (runId, key, value) => this.updateRunState(runId, key, value),
|
|
1015
1028
|
resumeWorkflow: (runId) => this.resumeWorkflow(runId),
|
|
1016
1029
|
scheduleRunWake: (runId, delay) => this.scheduleRunWake(runId, delay),
|
|
1030
|
+
getRunOwner: async (runId) => (await this.getRunIdentity(runId))?.wire?.pikkuUserId,
|
|
1031
|
+
auditApproval: (event) => auditApprovalDecision(event),
|
|
1017
1032
|
};
|
|
1018
1033
|
}
|
|
1019
1034
|
async approveStep(runId, reason, decision, session) {
|
|
1020
|
-
|
|
1021
|
-
return recordApprovalDecision(this.approvalStore, runId, reason, decision);
|
|
1035
|
+
return recordApprovalDecision(this.approvalStore, runId, reason, decision, approvalDeciderFrom(session));
|
|
1022
1036
|
}
|
|
1023
1037
|
async approvalStep(runId, reason, options) {
|
|
1024
1038
|
const fromStepName = this.lastStepName(runId);
|
|
@@ -210,6 +210,14 @@ export interface ScenarioBrowserProvider {
|
|
|
210
210
|
* scenario cannot leave the next signed in as somebody else.
|
|
211
211
|
*/
|
|
212
212
|
reset?(): Promise<void>;
|
|
213
|
+
/**
|
|
214
|
+
* Name the scenario about to run, so anything it captures is filed under it.
|
|
215
|
+
*
|
|
216
|
+
* Called before each scenario. A provider that is never told has to fall
|
|
217
|
+
* back to one shared label, which puts every run's artifacts in a single
|
|
218
|
+
* folder — findable only by timestamp.
|
|
219
|
+
*/
|
|
220
|
+
beginScenario?(scenario: string): void;
|
|
213
221
|
/**
|
|
214
222
|
* Snapshot every open window for a failed scenario. `label` identifies the
|
|
215
223
|
* scenario in artifact filenames. Never throws: a failure to capture must
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CorePikkuFunctionConfig, CorePikkuFunctionHook } from '../../function/functions.types.js';
|
|
2
|
+
export type { ScenarioStepInvocation, ScenarioStepMeta, PikkuScenarioWire, } from './dsl/workflow-dsl.types.js';
|
|
3
|
+
export type { ScenarioStepPhase, ScenarioStepKind, ScenarioStepOptions, PikkuScenarioStepWire, ScenarioEnvironment, ScenarioSurface, ScenarioSurfaceResolution, PikkuBrowserWire, TestIdSelector, ScenarioBrowserProvider, ScenarioBrowserFailure, } from './scenario-step.types.js';
|
|
4
|
+
export type CoreFeatureScenario = CorePikkuFunctionConfig<any, any, any> | {
|
|
5
|
+
scenario: CorePikkuFunctionConfig<any, any, any>;
|
|
6
|
+
data: unknown;
|
|
7
|
+
};
|
|
8
|
+
export type CoreFeature = {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
tags?: string[];
|
|
12
|
+
scenarios: readonly CoreFeatureScenario[];
|
|
13
|
+
before?: CorePikkuFunctionHook;
|
|
14
|
+
after?: CorePikkuFunctionHook;
|
|
15
|
+
};
|
|
16
|
+
export type FeatureMetaEntry = {
|
|
17
|
+
scenario: string;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
};
|
|
20
|
+
export type FeatureMeta = {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
tags: string[];
|
|
25
|
+
entries: FeatureMetaEntry[];
|
|
26
|
+
unresolvedEntries: number;
|
|
27
|
+
hasBefore: boolean;
|
|
28
|
+
hasAfter: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type FeaturesMeta = Record<string, FeatureMeta>;
|
|
31
|
+
export type FeaturePlanEntry = {
|
|
32
|
+
featureId: string;
|
|
33
|
+
featureName: string;
|
|
34
|
+
scenarioName: string;
|
|
35
|
+
data?: unknown;
|
|
36
|
+
tags: string[];
|
|
37
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ApprovalAuditEvent } from './workflow-approval.js';
|
|
2
|
+
/** The audit type an answer to an approval gate is recorded under. */
|
|
3
|
+
export declare const APPROVAL_AUDIT_TYPE = "workflow.approval.decided";
|
|
4
|
+
/**
|
|
5
|
+
* Record an answer to an approval gate where it outlives the run.
|
|
6
|
+
*
|
|
7
|
+
* The step result carries the settled decision, but it is deleted with the run
|
|
8
|
+
* — `deleteRun` cascades to steps and to history — and a refused attempt never
|
|
9
|
+
* reaches a step at all. An approval is asked for precisely so it can be
|
|
10
|
+
* answered for afterwards, so the answer also goes to the audit sink, which
|
|
11
|
+
* holds no foreign key to the run.
|
|
12
|
+
*
|
|
13
|
+
* A project with no sink wired records nothing, and a sink that fails is logged
|
|
14
|
+
* rather than thrown: the trail must not be the reason a decision is lost.
|
|
15
|
+
*/
|
|
16
|
+
export declare const auditApprovalDecision: (event: ApprovalAuditEvent) => Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getSingletonServices } from '../../pikku-state.js';
|
|
2
|
+
/** The audit type an answer to an approval gate is recorded under. */
|
|
3
|
+
export const APPROVAL_AUDIT_TYPE = 'workflow.approval.decided';
|
|
4
|
+
/**
|
|
5
|
+
* Record an answer to an approval gate where it outlives the run.
|
|
6
|
+
*
|
|
7
|
+
* The step result carries the settled decision, but it is deleted with the run
|
|
8
|
+
* — `deleteRun` cascades to steps and to history — and a refused attempt never
|
|
9
|
+
* reaches a step at all. An approval is asked for precisely so it can be
|
|
10
|
+
* answered for afterwards, so the answer also goes to the audit sink, which
|
|
11
|
+
* holds no foreign key to the run.
|
|
12
|
+
*
|
|
13
|
+
* A project with no sink wired records nothing, and a sink that fails is logged
|
|
14
|
+
* rather than thrown: the trail must not be the reason a decision is lost.
|
|
15
|
+
*/
|
|
16
|
+
export const auditApprovalDecision = async (event) => {
|
|
17
|
+
const services = getSingletonServices();
|
|
18
|
+
if (!services?.audit) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
await services.audit.audit({
|
|
23
|
+
type: APPROVAL_AUDIT_TYPE,
|
|
24
|
+
source: 'explicit',
|
|
25
|
+
outcome: event.outcome,
|
|
26
|
+
occurredAt: new Date().toISOString(),
|
|
27
|
+
wireType: 'workflow',
|
|
28
|
+
userIdentity: { pikkuUserId: event.decidedBy?.userId },
|
|
29
|
+
metadata: {
|
|
30
|
+
runId: event.runId,
|
|
31
|
+
reason: event.reason,
|
|
32
|
+
scopes: event.decidedBy?.scopes,
|
|
33
|
+
refusal: event.refusal,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
services.logger?.warn(`Failed to audit the decision on approval '${event.reason}' for run ${event.runId}`, error);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ForbiddenError } from '../../errors/errors.js';
|
|
2
|
+
import type { CoreUserSession } from '../../types/core.types.js';
|
|
3
|
+
import type { ApprovalDecider, WorkflowApprovalPolicy } from './dsl/workflow-dsl.types.js';
|
|
4
|
+
export declare class WorkflowApprovalForbiddenError extends ForbiddenError {
|
|
5
|
+
payload: {
|
|
6
|
+
reason: string;
|
|
7
|
+
detail: string;
|
|
8
|
+
};
|
|
9
|
+
constructor(reason: string, detail: string);
|
|
10
|
+
}
|
|
11
|
+
export declare const approvalDeciderFrom: (session: CoreUserSession | undefined) => ApprovalDecider | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Judge a decision against the gate's declared policy, returning the reason it
|
|
14
|
+
* is refused or `undefined` if it stands.
|
|
15
|
+
*
|
|
16
|
+
* Returns a message rather than throwing because the same judgement is needed
|
|
17
|
+
* in two places with different outcomes: refusing a live submission with a 403,
|
|
18
|
+
* and clearing an already-recorded decision on replay.
|
|
19
|
+
*/
|
|
20
|
+
export declare const approvalPolicyRefusal: (policy: WorkflowApprovalPolicy, owner: string | undefined, decider: ApprovalDecider | undefined) => string | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ForbiddenError } from '../../errors/errors.js';
|
|
2
|
+
import { addError } from '../../errors/error-handler.js';
|
|
3
|
+
export class WorkflowApprovalForbiddenError extends ForbiddenError {
|
|
4
|
+
payload;
|
|
5
|
+
constructor(reason, detail) {
|
|
6
|
+
super(detail);
|
|
7
|
+
this.payload = { reason, detail };
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
addError(WorkflowApprovalForbiddenError, {
|
|
11
|
+
status: 403,
|
|
12
|
+
message: 'Not authorized to answer this approval.',
|
|
13
|
+
});
|
|
14
|
+
export const approvalDeciderFrom = (session) => session ? { userId: session.userId, scopes: session.scopes } : undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Judge a decision against the gate's declared policy, returning the reason it
|
|
17
|
+
* is refused or `undefined` if it stands.
|
|
18
|
+
*
|
|
19
|
+
* Returns a message rather than throwing because the same judgement is needed
|
|
20
|
+
* in two places with different outcomes: refusing a live submission with a 403,
|
|
21
|
+
* and clearing an already-recorded decision on replay.
|
|
22
|
+
*/
|
|
23
|
+
export const approvalPolicyRefusal = (policy, owner, decider) => {
|
|
24
|
+
if (policy.approverScope &&
|
|
25
|
+
!decider?.scopes?.includes(policy.approverScope)) {
|
|
26
|
+
return `Answering this approval requires the '${policy.approverScope}' scope`;
|
|
27
|
+
}
|
|
28
|
+
switch (policy.approvers ?? 'any') {
|
|
29
|
+
case 'owner':
|
|
30
|
+
if (!owner) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (!decider?.userId || decider.userId !== owner) {
|
|
34
|
+
return 'Only the user who started this run may answer this approval';
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
case 'not-initiator':
|
|
38
|
+
if (!decider?.userId) {
|
|
39
|
+
return 'Answering this approval requires a signed-in user';
|
|
40
|
+
}
|
|
41
|
+
if (owner && decider.userId === owner) {
|
|
42
|
+
return 'The user who started this run may not answer this approval';
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
case 'any':
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
};
|