@lostgradient/weft 0.13.0 → 0.14.0
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/README.md +1 -1
- package/dist/cli/help-text.d.ts +3 -3
- package/dist/cli/help-text.js +4 -4
- package/dist/cli/operation-catalog-snapshot.d.ts +1 -1
- package/dist/cli/schedule.js +1 -3
- package/dist/cli/serve-registrations.d.ts +3 -3
- package/dist/cli/serve-registrations.js +2 -2
- package/dist/cli/shutdown.d.ts +11 -0
- package/dist/cli/shutdown.js +25 -0
- package/dist/cli/validate.d.ts +1 -1
- package/dist/cli/version-check.d.ts +1 -1
- package/dist/cli/version-check.js +1 -1
- package/dist/cli-main.js +10 -29
- package/dist/client/http-client-storage.js +7 -35
- package/dist/client/http-request.d.ts +8 -9
- package/dist/client/http-request.js +3 -25
- package/dist/core/context/parallel-operations.d.ts +0 -2
- package/dist/core/context/parallel-operations.js +0 -7
- package/dist/core/engine/callback-checkpoint-persistence.d.ts +2 -0
- package/dist/core/engine/callback-checkpoint-persistence.js +6 -3
- package/dist/core/engine/callback-creators.js +4 -22
- package/dist/core/engine/checkpoint-io.d.ts +1 -1
- package/dist/core/engine/operations-coordination.js +1 -1
- package/dist/core/engine/parallel-dispatch.d.ts +1 -1
- package/dist/core/engine/parallel-dispatch.js +1 -1
- package/dist/core/engine/review-list-entries.d.ts +1 -1
- package/dist/core/engine/review-list-entries.js +6 -30
- package/dist/core/engine/timeline-coordinator-detail.d.ts +1 -1
- package/dist/core/review/index.d.ts +87 -0
- package/dist/core/review/index.js +29 -0
- package/dist/diagnostics/validate.d.ts +9 -30
- package/dist/diagnostics/validate.js +83 -21
- package/dist/diagnostics/version-check.d.ts +2 -2
- package/dist/http.js +2 -2
- package/dist/server/openapi-error-responses.js +0 -14
- package/dist/server/operation-catalog/index.d.ts +1 -1
- package/dist/server/operation-catalog/raise-fault.d.ts +2 -1
- package/dist/server/operation-catalog/types.d.ts +2 -1
- package/dist/server/operation-fault.d.ts +1 -4
- package/dist/server/operation-fault.js +1 -1
- package/dist/server/operation-registry.d.ts +1 -1
- package/dist/server/operations/list-reviews.js +2 -19
- package/dist/server/operations/recover-all.js +1 -10
- package/dist/server/operations/single-workflow-control-operation.d.ts +2 -1
- package/dist/storage/bounded-ndjson-response.d.ts +5 -0
- package/dist/storage/bounded-ndjson-response.js +33 -0
- package/dist/storage/http.js +5 -37
- package/dist/storage/neon.d.ts +3 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module human-review
|
|
8
8
|
*/
|
|
9
|
+
import { z } from 'zod';
|
|
9
10
|
import type { BatchOperation, Storage } from '../../storage/interface.ts';
|
|
10
11
|
import { WeftError } from '../weft-error.ts';
|
|
11
12
|
/**
|
|
@@ -51,6 +52,92 @@ export interface ReviewDecisionRecord {
|
|
|
51
52
|
sectionDecisions?: Record<string, 'approved' | 'rejected'>;
|
|
52
53
|
timestamp: number;
|
|
53
54
|
}
|
|
55
|
+
/** Canonical schema for a persisted review request without its storage envelope. */
|
|
56
|
+
export declare const reviewRequestSchema: z.ZodObject<{
|
|
57
|
+
reviewId: z.ZodString;
|
|
58
|
+
workflowId: z.ZodString;
|
|
59
|
+
artifact: z.ZodNonOptional<z.ZodUnknown>;
|
|
60
|
+
reviewType: z.ZodString;
|
|
61
|
+
reviewers: z.ZodArray<z.ZodString>;
|
|
62
|
+
allowPartial: z.ZodBoolean;
|
|
63
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
64
|
+
webhookUrl: z.ZodOptional<z.ZodString>;
|
|
65
|
+
createdAt: z.ZodNumber;
|
|
66
|
+
}, z.core.$strip>;
|
|
67
|
+
/** Canonical schema for a pending review entry returned by review list surfaces. */
|
|
68
|
+
export declare const pendingReviewEntrySchema: z.ZodObject<{
|
|
69
|
+
reviewId: z.ZodString;
|
|
70
|
+
workflowId: z.ZodString;
|
|
71
|
+
artifact: z.ZodNonOptional<z.ZodUnknown>;
|
|
72
|
+
reviewType: z.ZodString;
|
|
73
|
+
reviewers: z.ZodArray<z.ZodString>;
|
|
74
|
+
allowPartial: z.ZodBoolean;
|
|
75
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
76
|
+
webhookUrl: z.ZodOptional<z.ZodString>;
|
|
77
|
+
createdAt: z.ZodNumber;
|
|
78
|
+
status: z.ZodLiteral<"pending">;
|
|
79
|
+
}, z.core.$strip>;
|
|
80
|
+
/** Canonical schema for a completed review entry returned by review list surfaces. */
|
|
81
|
+
export declare const completedReviewEntrySchema: z.ZodObject<{
|
|
82
|
+
decision: z.ZodEnum<{
|
|
83
|
+
approved: "approved";
|
|
84
|
+
rejected: "rejected";
|
|
85
|
+
"needs-changes": "needs-changes";
|
|
86
|
+
}>;
|
|
87
|
+
reviewer: z.ZodString;
|
|
88
|
+
feedback: z.ZodOptional<z.ZodString>;
|
|
89
|
+
sectionDecisions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
90
|
+
approved: "approved";
|
|
91
|
+
rejected: "rejected";
|
|
92
|
+
}>>>;
|
|
93
|
+
timestamp: z.ZodNumber;
|
|
94
|
+
reviewId: z.ZodString;
|
|
95
|
+
workflowId: z.ZodString;
|
|
96
|
+
artifact: z.ZodNonOptional<z.ZodUnknown>;
|
|
97
|
+
reviewType: z.ZodString;
|
|
98
|
+
reviewers: z.ZodArray<z.ZodString>;
|
|
99
|
+
allowPartial: z.ZodBoolean;
|
|
100
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
101
|
+
webhookUrl: z.ZodOptional<z.ZodString>;
|
|
102
|
+
createdAt: z.ZodNumber;
|
|
103
|
+
status: z.ZodLiteral<"completed">;
|
|
104
|
+
}, z.core.$strip>;
|
|
105
|
+
/** Canonical schema for the discriminated review list-entry union. */
|
|
106
|
+
export declare const reviewListEntrySchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
107
|
+
reviewId: z.ZodString;
|
|
108
|
+
workflowId: z.ZodString;
|
|
109
|
+
artifact: z.ZodNonOptional<z.ZodUnknown>;
|
|
110
|
+
reviewType: z.ZodString;
|
|
111
|
+
reviewers: z.ZodArray<z.ZodString>;
|
|
112
|
+
allowPartial: z.ZodBoolean;
|
|
113
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
114
|
+
webhookUrl: z.ZodOptional<z.ZodString>;
|
|
115
|
+
createdAt: z.ZodNumber;
|
|
116
|
+
status: z.ZodLiteral<"pending">;
|
|
117
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
118
|
+
decision: z.ZodEnum<{
|
|
119
|
+
approved: "approved";
|
|
120
|
+
rejected: "rejected";
|
|
121
|
+
"needs-changes": "needs-changes";
|
|
122
|
+
}>;
|
|
123
|
+
reviewer: z.ZodString;
|
|
124
|
+
feedback: z.ZodOptional<z.ZodString>;
|
|
125
|
+
sectionDecisions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
126
|
+
approved: "approved";
|
|
127
|
+
rejected: "rejected";
|
|
128
|
+
}>>>;
|
|
129
|
+
timestamp: z.ZodNumber;
|
|
130
|
+
reviewId: z.ZodString;
|
|
131
|
+
workflowId: z.ZodString;
|
|
132
|
+
artifact: z.ZodNonOptional<z.ZodUnknown>;
|
|
133
|
+
reviewType: z.ZodString;
|
|
134
|
+
reviewers: z.ZodArray<z.ZodString>;
|
|
135
|
+
allowPartial: z.ZodBoolean;
|
|
136
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
137
|
+
webhookUrl: z.ZodOptional<z.ZodString>;
|
|
138
|
+
createdAt: z.ZodNumber;
|
|
139
|
+
status: z.ZodLiteral<"completed">;
|
|
140
|
+
}, z.core.$strip>]>;
|
|
54
141
|
/**
|
|
55
142
|
* One step in a {@link ReviewOptions.escalation} chain. Either reassigns the
|
|
56
143
|
* pending review to a new owner (`to`) or auto-decides it (`action`) after
|
|
@@ -1,7 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { KEYS } from "../../storage/interface.js";
|
|
2
3
|
import { decode, encode } from "../codec.js";
|
|
3
4
|
import { WeftError } from "../weft-error.js";
|
|
4
5
|
import { ReviewRequestedEvent } from "./events.js";
|
|
6
|
+
const reviewRequestFields = {
|
|
7
|
+
reviewId: z.string(),
|
|
8
|
+
workflowId: z.string(),
|
|
9
|
+
artifact: z.unknown().nonoptional(),
|
|
10
|
+
reviewType: z.string(),
|
|
11
|
+
reviewers: z.array(z.string()),
|
|
12
|
+
allowPartial: z.boolean(),
|
|
13
|
+
timeout: z.number().optional(),
|
|
14
|
+
webhookUrl: z.string().optional(),
|
|
15
|
+
createdAt: z.number()
|
|
16
|
+
}, reviewDecisionFields = {
|
|
17
|
+
decision: z.enum(["approved", "rejected", "needs-changes"]),
|
|
18
|
+
reviewer: z.string(),
|
|
19
|
+
feedback: z.string().optional(),
|
|
20
|
+
sectionDecisions: z.record(z.string(), z.enum(["approved", "rejected"])).optional(),
|
|
21
|
+
timestamp: z.number()
|
|
22
|
+
};
|
|
23
|
+
export const reviewRequestSchema = z.object(reviewRequestFields), pendingReviewEntrySchema = z.object({
|
|
24
|
+
status: z.literal("pending"),
|
|
25
|
+
...reviewRequestFields
|
|
26
|
+
}), completedReviewEntrySchema = z.object({
|
|
27
|
+
status: z.literal("completed"),
|
|
28
|
+
...reviewRequestFields,
|
|
29
|
+
...reviewDecisionFields
|
|
30
|
+
}), reviewListEntrySchema = z.union([
|
|
31
|
+
pendingReviewEntrySchema,
|
|
32
|
+
completedReviewEntrySchema
|
|
33
|
+
]);
|
|
5
34
|
|
|
6
35
|
export class ReviewTimeoutError extends WeftError {
|
|
7
36
|
reviewId;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Design-time workflow validation for `weft validate`.
|
|
3
3
|
*
|
|
4
|
-
* Analyses workflow
|
|
4
|
+
* Analyses workflow definitions for common anti-patterns:
|
|
5
5
|
*
|
|
6
6
|
* 1. **Unbounded retry policy** — an activity whose `retry.maxAttempts` is
|
|
7
7
|
* `Infinity` (or the workflow registration specifies `retry.maxAttempts`
|
|
@@ -21,27 +21,7 @@
|
|
|
21
21
|
*
|
|
22
22
|
* @module diagnostics/validate
|
|
23
23
|
*/
|
|
24
|
-
import type {
|
|
25
|
-
import type { ActivityDefinition, DefinitionSchema, RetentionPolicy, SearchAttributeSchema, WorkflowFunction } from '../core/types.ts';
|
|
26
|
-
/**
|
|
27
|
-
* Loosely-typed workflow registration shape used only by the `weft validate`
|
|
28
|
-
* and `weft schedule` CLIs when loading workflow modules from disk. This is
|
|
29
|
-
* intentionally separate from the public `WorkflowDefinition` type because
|
|
30
|
-
* loaded modules historically export bare `{ handler, ... }` objects that
|
|
31
|
-
* do not carry a `name` field — the registration key comes from the export
|
|
32
|
-
* key, not the object itself.
|
|
33
|
-
*/
|
|
34
|
-
export interface WorkflowRegistration<TInput = unknown, TOutput = unknown> {
|
|
35
|
-
version?: string;
|
|
36
|
-
description?: string;
|
|
37
|
-
tags?: ReadonlyArray<string>;
|
|
38
|
-
inputSchema?: DefinitionSchema<unknown, TInput>;
|
|
39
|
-
outputSchema?: DefinitionSchema<unknown, TOutput>;
|
|
40
|
-
handler: WorkflowFunction<TInput, TOutput>;
|
|
41
|
-
searchAttributes?: SearchAttributeSchema;
|
|
42
|
-
retention?: RetentionPolicy;
|
|
43
|
-
constraints?: ConstraintDefinition[];
|
|
44
|
-
}
|
|
24
|
+
import type { ActivityDefinition, WorkflowDefinition } from '../core/types.ts';
|
|
45
25
|
export type ValidationIssueSeverity = 'error' | 'warning';
|
|
46
26
|
export type ValidationIssueCode = 'unbounded-retry' | 'stateful-without-compensator' | 'non-serializable-input';
|
|
47
27
|
export interface ValidationIssue {
|
|
@@ -52,7 +32,7 @@ export interface ValidationIssue {
|
|
|
52
32
|
message: string;
|
|
53
33
|
}
|
|
54
34
|
export interface ValidationReport {
|
|
55
|
-
/** Total number of workflow
|
|
35
|
+
/** Total number of workflow definitions scanned. */
|
|
56
36
|
workflowCount: number;
|
|
57
37
|
/** All detected issues across all registrations. */
|
|
58
38
|
issues: ValidationIssue[];
|
|
@@ -60,16 +40,15 @@ export interface ValidationReport {
|
|
|
60
40
|
valid: boolean;
|
|
61
41
|
}
|
|
62
42
|
/**
|
|
63
|
-
* Validate a collection of workflow
|
|
43
|
+
* Validate a collection of workflow definitions for common anti-patterns.
|
|
64
44
|
*
|
|
65
|
-
* @param registrations A record of workflow type name
|
|
66
|
-
* @param activities Optional
|
|
67
|
-
*
|
|
68
|
-
* alone, so pass them explicitly when available.
|
|
45
|
+
* @param registrations A record of workflow type name to WorkflowDefinition.
|
|
46
|
+
* @param activities Optional standalone ActivityDefinition objects to check
|
|
47
|
+
* in addition to activities embedded in workflow definitions.
|
|
69
48
|
*/
|
|
70
|
-
export declare function validateRegistrations(registrations: Record<string,
|
|
49
|
+
export declare function validateRegistrations(registrations: Record<string, WorkflowDefinition>, activities?: ActivityDefinition[]): ValidationReport;
|
|
71
50
|
export declare function loadRegistrationsFromModule(modulePath: string): Promise<{
|
|
72
|
-
registrations: Record<string,
|
|
51
|
+
registrations: Record<string, WorkflowDefinition>;
|
|
73
52
|
activities: ActivityDefinition[];
|
|
74
53
|
}>;
|
|
75
54
|
/**
|
|
@@ -24,14 +24,11 @@ function checkStatefulWithoutCompensator(workflowType, activity) {
|
|
|
24
24
|
}
|
|
25
25
|
export function validateRegistrations(registrations, activities = []) {
|
|
26
26
|
const issues = [], workflowTypes = Object.keys(registrations);
|
|
27
|
-
for (const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (compensatorIssue)
|
|
33
|
-
issues.push(compensatorIssue);
|
|
34
|
-
}
|
|
27
|
+
for (const definition of Object.values(registrations))
|
|
28
|
+
for (const activity of workflowActivities(definition))
|
|
29
|
+
appendActivityIssues(issues, definition.name, activity);
|
|
30
|
+
for (const activity of activities)
|
|
31
|
+
appendActivityIssues(issues, "(standalone)", activity);
|
|
35
32
|
const hasErrors = issues.some((i) => i.severity === "error");
|
|
36
33
|
return {
|
|
37
34
|
workflowCount: workflowTypes.length,
|
|
@@ -39,26 +36,91 @@ export function validateRegistrations(registrations, activities = []) {
|
|
|
39
36
|
valid: !hasErrors
|
|
40
37
|
};
|
|
41
38
|
}
|
|
39
|
+
function appendActivityIssues(issues, workflowType, activity) {
|
|
40
|
+
const retryIssue = checkUnboundedRetry(workflowType, activity);
|
|
41
|
+
if (retryIssue)
|
|
42
|
+
issues.push(retryIssue);
|
|
43
|
+
const compensatorIssue = checkStatefulWithoutCompensator(workflowType, activity);
|
|
44
|
+
if (compensatorIssue)
|
|
45
|
+
issues.push(compensatorIssue);
|
|
46
|
+
}
|
|
47
|
+
function workflowActivities(definition) {
|
|
48
|
+
if (!("activities" in definition) || !isObject(definition.activities))
|
|
49
|
+
return [];
|
|
50
|
+
return Object.values(definition.activities).filter(isActivityDefinition);
|
|
51
|
+
}
|
|
42
52
|
function collectFromExports(entries, registrations, activities, options) {
|
|
43
53
|
for (const [key, value] of entries)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
collectExport(key, value, registrations, activities, options);
|
|
55
|
+
}
|
|
56
|
+
function collectExport(key, value, registrations, activities, options) {
|
|
57
|
+
if (isWorkflowDefinition(value)) {
|
|
58
|
+
if (options.allowOverwrite || !Object.hasOwn(registrations, value.name))
|
|
59
|
+
registrations[value.name] = value;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (isActivityDefinition(value)) {
|
|
63
|
+
if (options.allowOverwrite || !activities.includes(value))
|
|
64
|
+
activities.push(value);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!isObject(value))
|
|
68
|
+
return;
|
|
69
|
+
if (options.depth === 0 && isDefinitionMap(value)) {
|
|
70
|
+
collectFromExports(Object.entries(value), registrations, activities, {
|
|
71
|
+
...options,
|
|
72
|
+
depth: options.depth + 1
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
assertNotRemovedWorkflowShape(key, value);
|
|
51
77
|
}
|
|
52
78
|
export async function loadRegistrationsFromModule(modulePath) {
|
|
53
|
-
const mod = await import(resolve(process.cwd(), modulePath)), registrations =
|
|
54
|
-
if (defaultExport !==
|
|
55
|
-
collectFromExports(
|
|
79
|
+
const mod = await import(resolve(process.cwd(), modulePath)), registrations = Object.create(null), activities = [], defaultExport = mod.default;
|
|
80
|
+
if (defaultExport !== void 0)
|
|
81
|
+
collectFromExports([["default", defaultExport]], registrations, activities, {
|
|
82
|
+
allowOverwrite: !0,
|
|
83
|
+
depth: 0
|
|
84
|
+
});
|
|
56
85
|
const namedEntries = Object.entries(mod).filter(([key]) => key !== "default");
|
|
57
|
-
collectFromExports(namedEntries, registrations, activities, {
|
|
86
|
+
collectFromExports(namedEntries, registrations, activities, {
|
|
87
|
+
allowOverwrite: !1,
|
|
88
|
+
depth: 0
|
|
89
|
+
});
|
|
58
90
|
return { registrations, activities };
|
|
59
91
|
}
|
|
60
|
-
function
|
|
61
|
-
return typeof value === "object" && value !== null
|
|
92
|
+
function isObject(value) {
|
|
93
|
+
return typeof value === "object" && value !== null;
|
|
94
|
+
}
|
|
95
|
+
function isRemovedWorkflowShape(value) {
|
|
96
|
+
if (!isObject(value) || !Object.hasOwn(value, "handler"))
|
|
97
|
+
return !1;
|
|
98
|
+
const handler = value.handler;
|
|
99
|
+
if (typeof handler !== "function")
|
|
100
|
+
return !1;
|
|
101
|
+
const functionKind = Object.prototype.toString.call(handler);
|
|
102
|
+
if (functionKind === "[object AsyncGeneratorFunction]")
|
|
103
|
+
return !0;
|
|
104
|
+
if ([
|
|
105
|
+
"version",
|
|
106
|
+
"description",
|
|
107
|
+
"tags",
|
|
108
|
+
"inputSchema",
|
|
109
|
+
"outputSchema",
|
|
110
|
+
"searchAttributes"
|
|
111
|
+
].some((key) => Object.hasOwn(value, key)))
|
|
112
|
+
return !0;
|
|
113
|
+
return functionKind === "[object Function]" && Object.keys(value).length === 1;
|
|
114
|
+
}
|
|
115
|
+
function assertNotRemovedWorkflowShape(exportName, value) {
|
|
116
|
+
if (isRemovedWorkflowShape(value))
|
|
117
|
+
throw TypeError(`Workflow export "${exportName}" must be a builder-produced workflow definition with its own name. Create it with \`workflow({ name }).execute(handler)\`.`);
|
|
118
|
+
}
|
|
119
|
+
function isDefinitionMap(value) {
|
|
120
|
+
return Object.values(value).some((entry) => isWorkflowDefinition(entry) || isActivityDefinition(entry) || isRemovedWorkflowShape(entry));
|
|
121
|
+
}
|
|
122
|
+
function isWorkflowDefinition(value) {
|
|
123
|
+
return isObject(value) && typeof value.name === "string" && "handler" in value && typeof value.handler === "function";
|
|
62
124
|
}
|
|
63
125
|
function isActivityDefinition(value) {
|
|
64
126
|
if (value === null || typeof value !== "object" && typeof value !== "function")
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module diagnostics/version-check
|
|
9
9
|
*/
|
|
10
|
+
import type { WorkflowDefinition } from '../core/types.ts';
|
|
10
11
|
import type { Storage } from '../storage/interface.ts';
|
|
11
12
|
import type { VersionCheckReport } from './types.ts';
|
|
12
|
-
import type { WorkflowRegistration } from './validate.ts';
|
|
13
13
|
/**
|
|
14
14
|
* Scans active (running and pending) workflows in `storage`, groups them by
|
|
15
15
|
* type, and compares stored workflow versions against currently registered
|
|
@@ -31,4 +31,4 @@ import type { WorkflowRegistration } from './validate.ts';
|
|
|
31
31
|
* console.log(report.overallVerdict); // 'safe'
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
|
-
export declare function runVersionCheck(storage: Storage, registrations: Record<string,
|
|
34
|
+
export declare function runVersionCheck(storage: Storage, registrations: Record<string, WorkflowDefinition>): Promise<VersionCheckReport>;
|
package/dist/http.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function
|
|
2
|
-
`);
|
|
1
|
+
async function*R(F,G){if(F.body===null)return;let J=F.body.getReader(),Q=new TextDecoder,Z="",$=0,j=!1;try{while(!0){let{done:y,value:z}=await J.read();if(y){j=!0;break}if($+=z.byteLength,$>G.maximumBytes)throw G.sizeLimitError();Z+=Q.decode(z,{stream:!0});let A=Z.split(`
|
|
2
|
+
`);Z=A.pop()??"";for(let g of A)yield g}if(Z+=Q.decode(),Z.length>0)yield Z}finally{if(!j)try{await J.cancel()}catch{}J.releaseLock()}}function N(F){let G=[];for(let J=0;J<F.length;J+=512)G.push(String.fromCharCode(...F.subarray(J,J+512)));return btoa(G.join(""))}function B(F){let G=atob(F),J=new Uint8Array(G.length);for(let Q=0;Q<G.length;Q+=1)J[Q]=G.charCodeAt(Q);return J}function q(F){return typeof F==="object"&&F!==null&&!Array.isArray(F)}async function _(F,G){return await F.get(G)!==null}async function*X(F,G,J){for await(let[Q]of F.scan(G,J))yield Q}async function H(F,G){let J=0;for await(let Q of X(F,G))J++;return J}async function L(F,G){let J=[];for await(let Q of X(F,G))J.push({type:"delete",key:Q});if(J.length===0)return 0;return await F.batch(J),J.length}async function U(F,G,J){let Q=[];for await(let Z of X(F,G,J))Q.push({type:"delete",key:Z});if(Q.length===0)return 0;return await F.batch(Q),Q.length}function C(F,G,J){if(!F.capabilities()[G])throw Error(`Feature "${J}" requires storage capability "${G}", but this storage backend does not provide it.`)}var D=1e4;class T extends Error{code="StorageBatchOperationLimitExceededError";cap=D;count;target;constructor(F,G){super(`${F} count ${G} exceeds MAX_BATCH_OPERATIONS (${D}).`);this.name="StorageBatchOperationLimitExceededError",this.target=F,this.count=G}}function M(F,G){if(G>D)throw new T(F,G)}function S(F){return F.length>0?F.slice(0,-1)+String.fromCharCode(F.charCodeAt(F.length-1)+1):"ÿ"}function e(F,G={}){if(G.gt!==void 0&&F<=G.gt)return!1;if(G.gte!==void 0&&F<G.gte)return!1;if(G.lt!==void 0&&F>=G.lt)return!1;if(G.lte!==void 0&&F>G.lte)return!1;return!0}function FF(F,G){if(F===null||G===null)return F===G;if(F.byteLength!==G.byteLength)return!1;for(let J=0;J<F.byteLength;J++)if(F[J]!==G[J])return!1;return!0}async function K(F,G){if(F.has)return F.has(G);return _(F,G)}function P(F,G,J){if(F.keys)return F.keys(G,J);return X(F,G,J)}async function O(F,G){if(F.count)return F.count(G);return H(F,G)}async function b(F,G){if(F.deletePrefix)return F.deletePrefix(G);return L(F,G)}async function v(F,G,J){if(M("conditionalBatch conditions",G.length),M("conditionalBatch operations",J.length),C(F,"conditionalBatch","storageConditionalBatch"),!F.conditionalBatch)throw Error("This storage backend reports conditionalBatch capability but does not implement the conditionalBatch() method.");return F.conditionalBatch(G,J)}function c(F){if(F===void 0)return;if(typeof F!=="number"||!Number.isInteger(F)||F<0)throw Error("deleteRange limit must be a finite non-negative integer");return F===0?0:F}function V(F){let G={},J=!1;for(let Z of["gt","gte","lt","lte"]){let $=F[Z];if($===void 0)continue;if(typeof $!=="string")throw Error("deleteRange bounds must be strings");G[Z]=$,J=!0}if(!J)throw Error("deleteRange requires at least one of gt/gte/lt/lte; use deletePrefix to delete a whole prefix");let Q=c(F.limit);if(Q!==void 0)G.limit=Q;return G}async function h(F,G,J){let Q=V(J);if(F.deleteRange)return F.deleteRange(G,Q);return U(F,G,Q)}function I(F,G){let J={key:F,open:!1};if(G.gte!==void 0&&G.gte>J.key)J.key=G.gte,J.open=!1;if(G.gt!==void 0&&G.gt>=J.key)J.key=G.gt,J.open=!0;return J}function u(F,G){let J={key:S(F),open:!0};if(G.lt!==void 0&&G.lt<=J.key)J.key=G.lt,J.open=!0;if(G.lte!==void 0&&G.lte<J.key)J.key=G.lte,J.open=!1;return J}function ZF(F,G){let J=I(F,G),Q=u(F,G);if(J.key>Q.key||J.key===Q.key&&(J.open||Q.open))return null;return{lower:J,upper:Q}}function E(F){return F.replaceAll(/:+$/g,"")}function k(F,G){let J=E(F),Q=E(G);if(J.length===0)return Q;if(Q.length===0)return J;return`${J}:${Q}`}class W{#G;#J;constructor(F,G){this.#G=F,this.#J=E(G)}#F(F){if(this.#J.length===0)return F;return F.length===0?`${this.#J}:`:`${this.#J}:${F}`}#Q(F){if(this.#J.length===0)return F;return F.slice(this.#J.length+1)}#Z(F={}){let G={};if(F.limit!==void 0)G.limit=F.limit;if(F.reverse!==void 0)G.reverse=F.reverse;if(F.gt!==void 0)G.gt=this.#F(F.gt);if(F.gte!==void 0)G.gte=this.#F(F.gte);if(F.lt!==void 0)G.lt=this.#F(F.lt);if(F.lte!==void 0)G.lte=this.#F(F.lte);return G}#M(F){let G={};if(F.limit!==void 0)G.limit=F.limit;if(F.gt!==void 0)G.gt=this.#F(F.gt);if(F.gte!==void 0)G.gte=this.#F(F.gte);if(F.lt!==void 0)G.lt=this.#F(F.lt);if(F.lte!==void 0)G.lte=this.#F(F.lte);return G}capabilities(){return this.#G.capabilities()}scoped(F){return new W(this.#G,k(this.#J,F))}async get(F){return this.#G.get(this.#F(F))}async put(F,G){await this.#G.put(this.#F(F),G)}async delete(F){await this.#G.delete(this.#F(F))}async*scan(F,G){for await(let[J,Q]of this.#G.scan(this.#F(F),this.#Z(G)))yield[this.#Q(J),Q]}async batch(F){M("batch operations",F.length),await this.#G.batch(F.map((G)=>{if(G.type==="put")return{type:"put",key:this.#F(G.key),value:G.value};return{type:"delete",key:this.#F(G.key)}}))}async conditionalBatch(F,G){return v(this.#G,F.map((J)=>({key:this.#F(J.key),expectedValue:J.expectedValue})),G.map((J)=>{if(J.type==="put")return{type:"put",key:this.#F(J.key),value:J.value};return{type:"delete",key:this.#F(J.key)}}))}async has(F){return K(this.#G,this.#F(F))}async deletePrefix(F){return b(this.#G,this.#F(F))}async deleteRange(F,G){let J=this.#M(V(G));return h(this.#G,this.#F(F),J)}async*keys(F,G){for await(let J of P(this.#G,this.#F(F),this.#Z(G)))yield this.#Q(J)}async count(F){return O(this.#G,this.#F(F))}[Symbol.dispose](){this.#G[Symbol.dispose]()}}function m(F,G){return new W(F,G)}var w=67108864;function x(F){if(F.type==="put")return{type:"put",key:F.key,value:N(F.value)};return{type:"delete",key:F.key}}function f(F){return{key:F.key,expectedValue:F.expectedValue===null?null:N(F.expectedValue)}}function d(F){if(!q(F)||typeof F.key!=="string"||typeof F.value!=="string")throw Error("HTTPStorage scan response contained an invalid NDJSON entry.");return{key:F.key,value:F.value}}function l(F){if(!q(F)||typeof F.applied!=="boolean")throw Error('HTTPStorage conditional batch response must include a boolean "applied" field.');return F.applied}function Y(F,G,J){if(J!==void 0)F.searchParams.set(G,String(J))}function p(F){if(F.trim().length===0)return null;let G=d(JSON.parse(F));return[G.key,B(G.value)]}class a{#G;#J;#F;constructor(F){this.#G=F.baseUrl instanceof URL?F.baseUrl:new URL(F.baseUrl),this.#J={...F.headers},this.#F=F.remoteConditionalBatch??!1}capabilities(){return{persistence:"remote",readAfterWrite:"eventual",scanConsistency:"best-effort",atomicBatch:!0,conditionalBatch:this.#F,boundedRangeDelete:!1}}#Q(F){let G=this.#G.href.endsWith("/")?this.#G.href:`${this.#G.href}/`;return new URL(F.replace(/^\/+/,""),G)}#Z(F){return this.#Q(`/v1/storage/${encodeURIComponent(F)}`)}#M(F,G){let J=this.#Q("/v1/storage");return J.searchParams.set("prefix",F),Y(J,"limit",G.limit),Y(J,"reverse",G.reverse),Y(J,"gt",G.gt),Y(J,"gte",G.gte),Y(J,"lt",G.lt),Y(J,"lte",G.lte),J}async#$(F,G={},J=[]){let Q=new Headers(this.#J);for(let[$,j]of new Headers(G.headers).entries())Q.set($,j);let Z=await fetch(F,{...G,headers:Q});if(!Z.ok&&!J.includes(Z.status))throw Error(`HTTPStorage request failed: ${G.method??"GET"} ${F.pathname} returned ${String(Z.status)}.`);return Z}async get(F){let G=await this.#$(this.#Z(F),{method:"GET"},[404]);if(G.status===404)return null;return new Uint8Array(await G.arrayBuffer())}async put(F,G){await this.#$(this.#Z(F),{method:"PUT",headers:{"content-type":"application/octet-stream"},body:new Blob([G])})}async delete(F){await this.#$(this.#Z(F),{method:"DELETE"})}async*scan(F,G={}){let J=await this.#$(this.#M(F,G),{method:"GET",headers:{accept:"application/x-ndjson"}});for await(let Q of R(J,{maximumBytes:w,sizeLimitError:()=>Error("HTTPStorage scan response exceeded the maximum allowed size.")})){let Z=p(Q);if(Z!==null)yield Z}}async batch(F){M("batch operations",F.length),await this.#$(this.#Q("/v1/storage/-/batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({operations:F.map(x)})})}async conditionalBatch(F,G){M("conditionalBatch conditions",F.length),M("conditionalBatch operations",G.length);let J=await this.#$(this.#Q("/v1/storage/-/conditional-batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({conditions:F.map(f),operations:G.map(x)})});return l(await J.json())}has(F){return _(this,F)}async*keys(F,G){yield*X(this,F,G)}count(F){return H(this,F)}deletePrefix(F){return L(this,F)}deleteRange(F,G){return U(this,F,V(G))}scoped(F){return m(this,F)}[Symbol.dispose](){}}export{a as HTTPStorage};
|
|
@@ -10,20 +10,6 @@ export const ERROR_SCHEMA = {
|
|
|
10
10
|
type: "string",
|
|
11
11
|
description: "Fine-grained public Weft error code when one is available"
|
|
12
12
|
},
|
|
13
|
-
missingTypes: {
|
|
14
|
-
type: "array",
|
|
15
|
-
items: { type: "string" },
|
|
16
|
-
description: "Established recovery-conflict field; also present under data"
|
|
17
|
-
},
|
|
18
|
-
missingWorkflowCount: {
|
|
19
|
-
type: "integer",
|
|
20
|
-
minimum: 0,
|
|
21
|
-
description: "Established recovery-conflict field; also present under data"
|
|
22
|
-
},
|
|
23
|
-
samplesTruncated: {
|
|
24
|
-
type: "boolean",
|
|
25
|
-
description: "Established recovery-conflict field; also present under data"
|
|
26
|
-
},
|
|
27
13
|
data: {
|
|
28
14
|
type: "object",
|
|
29
15
|
description: "Audited fault-specific context; omitted when no fields are safe to expose",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module server/operation-catalog
|
|
10
10
|
*/
|
|
11
|
-
export type { FaultCode } from '
|
|
11
|
+
export type { FaultCode } from '../../core/fault-code.ts';
|
|
12
12
|
export { catalogActivities, catalogActivity } from './activity-adapter.ts';
|
|
13
13
|
export type { CatalogActivityDefinition } from './activity-adapter.ts';
|
|
14
14
|
export { DISPATCH_ALLOWLIST } from './dispatch-allowlist.ts';
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module server/operation-catalog/raise-fault
|
|
5
5
|
*/
|
|
6
|
-
import type { FaultCode
|
|
6
|
+
import type { FaultCode } from '../../core/fault-code.ts';
|
|
7
|
+
import type { OperationFault } from '../operation-fault.ts';
|
|
7
8
|
import type { ErasedOperation } from './types.ts';
|
|
8
9
|
/**
|
|
9
10
|
* Fault codes that every operation can raise through the shared pipeline
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import type { FaultCode } from '../../core/fault-code.ts';
|
|
2
3
|
import type { AccessPolicy } from '../authorization.ts';
|
|
3
|
-
import type {
|
|
4
|
+
import type { OperationFault, TransportKind } from '../operation-fault.ts';
|
|
4
5
|
import type { Principal } from '../principal.ts';
|
|
5
6
|
/**
|
|
6
7
|
* Regex for the canonical `weft.<segment>(.<segment>)+` operation-name form.
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import type { FaultCode } from '../core/fault-code.ts';
|
|
38
38
|
import { type WeftErrorCode } from '../core/weft-error.ts';
|
|
39
|
-
export type { FaultCode };
|
|
40
39
|
/** Transport identifiers as seen by `executeOperation`. */
|
|
41
40
|
export type TransportKind = 'http-rest' | 'jsonRpcHttp' | 'jsonRpcWebSocket' | 'jsonRpcStdio';
|
|
42
41
|
/** A flattened zod issue, kept loose so we don't pin a zod version here. */
|
|
@@ -170,10 +169,8 @@ export type RestFaultBody = {
|
|
|
170
169
|
* `{ "error": "Internal server error" }`, regardless of response overrides.
|
|
171
170
|
*/
|
|
172
171
|
export declare function shapeOperationFaultAsJson(fault: OperationFault, options?: RestFaultResponseOptions): Response;
|
|
173
|
-
/** Canonical
|
|
172
|
+
/** Canonical REST response used by bindings and route-dispatch fallback. */
|
|
174
173
|
export declare function shapeRestFaultAsJson(fault: OperationFault, options?: RestFaultResponseOptions): Response;
|
|
175
|
-
/** Build the audited body for bespoke REST shapers that retain extra legacy fields. */
|
|
176
|
-
export declare function shapeRestFaultBody(fault: OperationFault, message?: string): RestFaultBody;
|
|
177
174
|
/**
|
|
178
175
|
* JSON-RPC error code for each fault. Reserved codes (-32700..-32603) keep
|
|
179
176
|
* the spec meanings (`InvalidParams`, `MethodNotFound`); Weft domain codes
|
|
@@ -61,7 +61,7 @@ export function shapeRestFaultAsJson(fault, options = {}) {
|
|
|
61
61
|
headers: { "Content-Type": "application/json" }
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
function shapeRestFaultBody(fault, message) {
|
|
65
65
|
if (fault.code === "EngineFailure")
|
|
66
66
|
return { error: "Internal server error" };
|
|
67
67
|
const error = message ?? fault.message, weftCode = weftCodeFromFaultData(fault.data), data = restDataFromFault(fault), body = { error };
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* cast required at the registry boundary.
|
|
18
18
|
*/
|
|
19
19
|
import type { z } from 'zod';
|
|
20
|
+
import type { FaultCode } from '../core/fault-code.ts';
|
|
20
21
|
import type { AccessPolicy } from './authorization.ts';
|
|
21
22
|
import type { AuthorizationDecision, McpToolMetadata, OperationContext, OperationDefinition, OperationInvocationResult, ParameterizedAccessHint, TransportAvailability, UnknownKeyPolicy } from './operation-catalog/types.ts';
|
|
22
|
-
import type { FaultCode } from './operation-fault.ts';
|
|
23
23
|
export { isValidOperationName, validateOperationName } from './operation-catalog.ts';
|
|
24
24
|
/**
|
|
25
25
|
* Input shape for `defineOperation`. Mirrors `OperationDefinition` but
|
|
@@ -1,29 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { reviewListEntrySchema } from "../../core/review/index.js";
|
|
2
3
|
import { shapeOperationFaultAsJson } from "../operation-fault.js";
|
|
3
4
|
import { defineOperation } from "../operation-registry.js";
|
|
4
5
|
const reviewStatusSchema = z.enum(["pending", "completed"]), listReviewsInput = z.object({
|
|
5
6
|
status: reviewStatusSchema.optional(),
|
|
6
7
|
workflowId: z.string().min(1).optional(),
|
|
7
8
|
reviewType: z.string().min(1).optional()
|
|
8
|
-
}),
|
|
9
|
-
status: z.literal("pending"),
|
|
10
|
-
reviewId: z.string(),
|
|
11
|
-
workflowId: z.string(),
|
|
12
|
-
artifact: z.unknown().nonoptional(),
|
|
13
|
-
reviewType: z.string(),
|
|
14
|
-
reviewers: z.array(z.string()),
|
|
15
|
-
allowPartial: z.boolean(),
|
|
16
|
-
timeout: z.number().optional(),
|
|
17
|
-
webhookUrl: z.string().optional(),
|
|
18
|
-
createdAt: z.number()
|
|
19
|
-
}), completedReviewEntrySchema = pendingReviewEntrySchema.extend({
|
|
20
|
-
status: z.literal("completed"),
|
|
21
|
-
decision: z.enum(["approved", "rejected", "needs-changes"]),
|
|
22
|
-
reviewer: z.string(),
|
|
23
|
-
feedback: z.string().optional(),
|
|
24
|
-
sectionDecisions: z.record(z.string(), z.enum(["approved", "rejected"])).optional(),
|
|
25
|
-
timestamp: z.number()
|
|
26
|
-
}), reviewListEntrySchema = z.union([pendingReviewEntrySchema, completedReviewEntrySchema]), listReviewsOutput = z.object({
|
|
9
|
+
}), listReviewsOutput = z.object({
|
|
27
10
|
items: z.array(reviewListEntrySchema)
|
|
28
11
|
});
|
|
29
12
|
export const listReviewsOperation = defineOperation({
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { WorkflowTypeNotRegisteredForRecoveryError } from "../../core/engine.js";
|
|
3
|
-
import { shapeRestFaultBody } from "../operation-fault.js";
|
|
4
3
|
import { defineOperation } from "../operation-registry.js";
|
|
5
4
|
import { shapeRestFault } from "./operation-helpers.js";
|
|
6
5
|
const recoverAllInput = z.object({}), recoverAllOutput = z.object({
|
|
@@ -54,13 +53,5 @@ export const recoverAllOperation = defineOperation({
|
|
|
54
53
|
function shapeRecoverAllFault(fault) {
|
|
55
54
|
if (fault.code !== "Conflict" || fault.data.missingTypes === void 0)
|
|
56
55
|
return shapeRestFault(fault);
|
|
57
|
-
return
|
|
58
|
-
...shapeRestFaultBody(fault, "workflow_type_not_registered_for_recovery"),
|
|
59
|
-
missingTypes: fault.data.missingTypes,
|
|
60
|
-
missingWorkflowCount: fault.data.missingWorkflowCount,
|
|
61
|
-
samplesTruncated: fault.data.samplesTruncated
|
|
62
|
-
}), {
|
|
63
|
-
status: 409,
|
|
64
|
-
headers: { "Content-Type": "application/json" }
|
|
65
|
-
});
|
|
56
|
+
return shapeRestFault(fault, { message: "workflow_type_not_registered_for_recovery" });
|
|
66
57
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
2
|
import type { Engine } from '../../core/engine.ts';
|
|
3
|
+
import type { FaultCode } from '../../core/fault-code.ts';
|
|
3
4
|
import type { OperationDefinition } from '../operation-catalog.ts';
|
|
4
|
-
import type {
|
|
5
|
+
import type { OperationFault } from '../operation-fault.ts';
|
|
5
6
|
type SingleWorkflowControlInput = {
|
|
6
7
|
readonly workflowId: string;
|
|
7
8
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export async function* readBoundedNdjsonResponse(response, options) {
|
|
2
|
+
if (response.body === null)
|
|
3
|
+
return;
|
|
4
|
+
const reader = response.body.getReader(), decoder = new TextDecoder;
|
|
5
|
+
let bufferedText = "", bytesRead = 0, reachedEndOfStream = !1;
|
|
6
|
+
try {
|
|
7
|
+
while (!0) {
|
|
8
|
+
const { done, value } = await reader.read();
|
|
9
|
+
if (done) {
|
|
10
|
+
reachedEndOfStream = !0;
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
bytesRead += value.byteLength;
|
|
14
|
+
if (bytesRead > options.maximumBytes)
|
|
15
|
+
throw options.sizeLimitError();
|
|
16
|
+
bufferedText += decoder.decode(value, { stream: !0 });
|
|
17
|
+
const lines = bufferedText.split(`
|
|
18
|
+
`);
|
|
19
|
+
bufferedText = lines.pop() ?? "";
|
|
20
|
+
for (const line of lines)
|
|
21
|
+
yield line;
|
|
22
|
+
}
|
|
23
|
+
bufferedText += decoder.decode();
|
|
24
|
+
if (bufferedText.length > 0)
|
|
25
|
+
yield bufferedText;
|
|
26
|
+
} finally {
|
|
27
|
+
if (!reachedEndOfStream)
|
|
28
|
+
try {
|
|
29
|
+
await reader.cancel();
|
|
30
|
+
} catch {}
|
|
31
|
+
reader.releaseLock();
|
|
32
|
+
}
|
|
33
|
+
}
|