仿真测试台(后端):节点指派 + 全链 Trace —— 起单定向到指定账号,真实任务落工作台
用户定的形态:测试台是**后端检测活动与流程**的,真实任务照旧到工作台
(不然指定账号干嘛)—— 所以测试台只管「起单 + 看穿」,人那一环还是真的。
**节点指派**(迁移 0016 workflow.assignments):
- StartWorkflowInput 增 `assignments {节点id → 员工id}`;分派改三级取人:
①节点指派(点名的人在岗就是他)②员工组轮值 ③全员轮值 —— 指派是加法不是替换,
点名的人不在岗退回轮值(单不能没人接);事件里如实记 `(节点指派)`
- **顺带修一个真 bug**:`node_instance.assignee_id` 写了但 `listInstances` 从没读回来
(审批箱收件、员工面链条、治理列表全按它认人 —— 写了不读等于没写)
**全链 Trace**(新模块 trace,L2;迁移 0017 sys.trace_span):
- span 由 **Mastra Observability 组件**产出(执行不经 Mastra 图,所以手动打点),
一开就落库、一收就补 —— 观测面不该比被观测的事晚(不走 exporter 的批量异步路)
- 打点覆盖:工作流 root → 节点(记着派给了谁)→ 活动内每次工具调用 / 模型调用 / 人卡
- **root span 挂在 `runtime.begin`**:那是「单已建好、还没进第一个节点」那一刻 ——
挂在 start 返回之后就晚了,起单同步走到第一个节点,节点 span 会先出生、另起孤树(实测)
- 观测尽力而为:写 span 失败吞掉,不掀翻业务
- 三样东西各管各的:trace(机器读时间轴)/ 任务记忆(人读留痕)/
**工作区报告(活动自持的 HTML)** —— 谁也不替代谁
路由:`POST /api/gov/testbed/run`(带指派起单)、`GET /api/gov/workflows/:id/trace`。
端到端六例(真库):指派生效 / 任务真落到被指派人手上并由他答单 / 下游节点同样生效 /
全链 span 逐层挂得上且耗时齐备 / 一条流一个 traceId / 不指派照旧轮值。五闸全过。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -25,6 +25,11 @@ export type StartWorkflowInput = {
|
||||
by?: string;
|
||||
};
|
||||
readonly data: Record<string, unknown>;
|
||||
/**
|
||||
* 节点指派 `{ 节点id → 员工id }`(可选)—— 起单时把某几个节点定向给指定账号。
|
||||
* 不传即照旧:员工组轮值 / 全员轮值。指的人不在岗时退回轮值,单不能没人接。
|
||||
*/
|
||||
readonly assignments?: Record<string, string>;
|
||||
};
|
||||
export type ProcessService = {
|
||||
/**
|
||||
|
||||
@@ -15,3 +15,6 @@ export type { CompiledFlow, CompileHooks, RunResult } from './internal/adapter/w
|
||||
export { compilePackage } from './internal/adapter/workflow-compiler.ts';
|
||||
export type { MastraRunner, MastraRuntimeDeps } from './internal/adapter/mastra-runtime.ts';
|
||||
export { createMastraRuntime } from './internal/adapter/mastra-runtime.ts';
|
||||
/** 自持运行时(默认实现)—— 装配层要包一层观测时得拿得到它 */
|
||||
export type { SelfHostedRuntimeDeps } from './internal/runtime/self-hosted-runtime.ts';
|
||||
export { createSelfHostedRuntime } from './internal/runtime/self-hosted-runtime.ts';
|
||||
|
||||
@@ -29,6 +29,12 @@ export type WorkflowRecord = {
|
||||
* 留痕要能解释「当时为什么派给他」,而不是被今天的组织调整改写(同 AM 9.1 版本信封)。
|
||||
*/
|
||||
readonly workGroup?: Record<string, string>;
|
||||
/**
|
||||
* 节点指派 `{ 节点id → 员工id }` —— **起单时定死某几个节点归谁**。
|
||||
* 分派仍是运营时的事:这只是在轮值之前插一句「这个节点就他了」,
|
||||
* 指的人不在岗则退回轮值(单不能没人接)。测试台用它把整条流走给指定账号。
|
||||
*/
|
||||
readonly assignments?: Record<string, string>;
|
||||
/** 各节点输出口载荷 —— 边映射装配的来源 */
|
||||
readonly ports: Record<string, Record<string, unknown>>;
|
||||
readonly vars: Record<string, unknown>;
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* module: trace —— **全链留痕的观测面**(设计台「每个活动完全 Trace 监控」)。
|
||||
*
|
||||
* 一条工作流 = 一条 trace;节点实例 = 一个 span;活动内的每一步、每次工具调用、
|
||||
* 每次模型调用、每张人卡 = 子 span。span 由 **Mastra Observability 组件**产出
|
||||
* (我们的执行不经 Mastra 图,所以是**手动打点**),再由自定义 exporter 落库。
|
||||
*
|
||||
* 与任务记忆的分工:任务记忆是**人读的留痕**(谁在何时做了什么、依据什么),
|
||||
* 一件事一行;trace 是**机器读的时间轴**(嵌套、耗时、入出参、错误),
|
||||
* 一次调用一个 span。两者并行,不互相替代。
|
||||
*/
|
||||
export type SpanKind = 'workflow' | 'node' | 'step' | 'tool' | 'model' | 'human';
|
||||
export type TraceSpan = {
|
||||
readonly id: string;
|
||||
readonly traceId: string;
|
||||
readonly parentId?: string;
|
||||
readonly name: string;
|
||||
readonly kind: SpanKind;
|
||||
readonly workflowId?: string;
|
||||
readonly nodeId?: string;
|
||||
readonly taskId?: string;
|
||||
readonly input?: Record<string, unknown>;
|
||||
readonly output?: Record<string, unknown>;
|
||||
readonly error?: {
|
||||
message: string;
|
||||
};
|
||||
readonly attributes?: Record<string, unknown>;
|
||||
readonly startedAt: string;
|
||||
readonly endedAt?: string;
|
||||
};
|
||||
/** 一次可观测的执行:开一个 span,干活,收尾 —— 收尾必做,不然时间轴上永远挂着 */
|
||||
export type SpanHandle = {
|
||||
readonly id: string;
|
||||
readonly traceId: string;
|
||||
child(input: {
|
||||
name: string;
|
||||
kind: SpanKind;
|
||||
nodeId?: string;
|
||||
taskId?: string;
|
||||
input?: Record<string, unknown>;
|
||||
attributes?: Record<string, unknown>;
|
||||
}): SpanHandle;
|
||||
end(out?: {
|
||||
output?: Record<string, unknown>;
|
||||
attributes?: Record<string, unknown>;
|
||||
}): Promise<void>;
|
||||
fail(error: unknown): Promise<void>;
|
||||
};
|
||||
export type TraceService = {
|
||||
/** 开一条工作流的 trace(root span) */
|
||||
beginWorkflow(input: {
|
||||
workflowId: string;
|
||||
subject: string;
|
||||
input?: Record<string, unknown>;
|
||||
}): void;
|
||||
/** 开一个节点 span(自动挂在工作流 span 下) */
|
||||
beginNode(input: {
|
||||
workflowId: string;
|
||||
nodeId: string;
|
||||
label: string;
|
||||
taskId?: string;
|
||||
approvalId?: string;
|
||||
assigneeId?: string;
|
||||
input?: Record<string, unknown>;
|
||||
}): void;
|
||||
/** 活动内的一次动作:步骤 / 工具 / 模型 / 人卡 —— 自动挂在该任务的节点 span 下 */
|
||||
beginAction(input: {
|
||||
taskId: string;
|
||||
name: string;
|
||||
kind: SpanKind;
|
||||
input?: Record<string, unknown>;
|
||||
}): SpanHandle | undefined;
|
||||
/** 收尾:节点终态 / 工作流终态 */
|
||||
endNode(input: {
|
||||
taskId?: string;
|
||||
workflowId?: string;
|
||||
nodeId?: string;
|
||||
output?: Record<string, unknown>;
|
||||
}): Promise<void>;
|
||||
endWorkflow(input: {
|
||||
workflowId: string;
|
||||
state: string;
|
||||
}): Promise<void>;
|
||||
/** 起一条 trace(工作流级)或挂在既有 trace 下 —— 低层口,便利层用它 */
|
||||
start(input: {
|
||||
name: string;
|
||||
kind: SpanKind;
|
||||
workflowId?: string;
|
||||
nodeId?: string;
|
||||
taskId?: string;
|
||||
parent?: {
|
||||
traceId: string;
|
||||
spanId: string;
|
||||
};
|
||||
input?: Record<string, unknown>;
|
||||
attributes?: Record<string, unknown>;
|
||||
}): SpanHandle;
|
||||
/** 这条工作流的完整 span 树(按开始时间序;树形由 parentId 拼) */
|
||||
ofWorkflow(workflowId: string): Promise<TraceSpan[]>;
|
||||
/** 这个任务(活动实例)的 span —— 活动内每一步都在这儿 */
|
||||
ofTask(taskId: string): Promise<TraceSpan[]>;
|
||||
flush(): Promise<void>;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* module `trace` 唯一出口(编码规范 §9.1)。
|
||||
* 全链观测:一条工作流一条 trace,节点/步骤/工具/模型/人卡逐层 span。
|
||||
*/
|
||||
export type { SpanHandle, SpanKind, TraceService, TraceSpan } from './contract.ts';
|
||||
export type { Clock, TraceStore } from './port.ts';
|
||||
export type { SpanFactory, TraceServiceDeps } from './internal/usecase/trace-service.ts';
|
||||
export { createTraceService } from './internal/usecase/trace-service.ts';
|
||||
export { createTraceStore } from './internal/adapter/trace-store.ts';
|
||||
export { traceSpanTable } from './internal/adapter/trace-schema.ts';
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/** trace 的存储端口 —— 落库形状由 adapter 决定,usecase 不认识 Drizzle */
|
||||
import type { TraceSpan } from './contract.ts';
|
||||
export type TraceStore = {
|
||||
/** 只增不改的两笔:开 span 写一行,收尾补 output/ended_at */
|
||||
insert(span: TraceSpan): Promise<void>;
|
||||
finish(id: string, patch: {
|
||||
output?: Record<string, unknown>;
|
||||
error?: {
|
||||
message: string;
|
||||
};
|
||||
attributes?: Record<string, unknown>;
|
||||
endedAt: string;
|
||||
}): Promise<void>;
|
||||
listByWorkflow(workflowId: string): Promise<TraceSpan[]>;
|
||||
listByTask(taskId: string): Promise<TraceSpan[]>;
|
||||
};
|
||||
export type Clock = {
|
||||
now(): Date;
|
||||
};
|
||||
Reference in New Issue
Block a user