Archived
三面拆分⑬:触发确认链路 + 运营中心「工作流」区 —— 设计只管设计,运营管细节
**触发确认链路**(用户口述五步的第 4/5 步):
接收邮件 → 分类识别 → 命中 RFQ 事件 → ④(可选,运营勾)系统消息到指定人
工作台(带邮件内容)→ ⑤ 工作台点「确认起单」,流程才起。
· 命中不等于必起:勾了「需人工确认」,装配好的入参存成待确认单
(sys.trigger_pending,migration 0019),ConfirmDesk 端口把带来件内容的
系统消息送指定人(eventRef 挂票号,消息列表就地贴确认状态与那枚钮)。
· 先认领后起单:consumed_at 空才认得到 —— 两人同时点、一人点两次,都只起一条流;
起单失败退回认领,不留死票。确认人记进 source.by,留痕可查。
· 登记触发不走确认(登记那一下本身就是人在确认);勾了没指定人 = 配置错误入死信。
· 4 条单测 + 全链实测:投信 → triggersHit=1 且零直起 → AI 工作台收到确认消息
→ 确认起单 → 二次确认幂等回同一流号 → source.by = AI。
**运营中心重排**(区名即运行时:工作流是流程包的运行时,任务是活动的运行时):
· 新「工作流」区:在途看板(从任务区迁入)+ 运行配置 —— 每包的触发器
起单开关 / 确认打勾 / 确认人,和节点负责人(sys.node_owner)。
· 负责人三层:显式指派 > 运营配置 > 轮值 —— 合并点在 process.start,
复用既有派发链一行不改。
· 任务区瘦身成活动运行时(任务/审批/需处置);知识库区改名「数据」
(与设计中心的知识库解开同名);系统区只剩通道实例。
· 设计中心触发器详情页摘掉启停按钮 —— 设计只管设计,启停/确认全在运营。
· 旧深链有改嫁:tasks/board → runs/board,tasks/trg → runs/conf。
reset-runs 补 sys.trigger_pending(待确认单是运行痕 —— 和上回漏 trace_span 同类洞)。
邮件分类 Agent(第 ② 步)现由抽键正则 + S-FEEL 代位,升级路径记进 debt.md。
产品设计.md 运营章、trigger/process 两份 CONTRACT.md 同步。
五闸绿;契约快照随本提交入库。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -63,3 +63,14 @@ export const nodeInstanceTable = sys.table(
|
||||
index('node_instance_task_idx').on(t.taskId),
|
||||
],
|
||||
);
|
||||
|
||||
/**
|
||||
* 节点负责人(**运营配置**,2026-07-30):流程包的哪个节点归谁。
|
||||
* 起单时垫在显式指派之下、轮值之上 —— 显式指派 > 这里 > 员工组轮值 > 全员轮值。
|
||||
* 不随包版本:它说的是「这家企业现在这活谁管」,不是流程长什么样。
|
||||
*/
|
||||
export const nodeOwnerTable = sys.table('node_owner', {
|
||||
packageId: text('package_id').notNull(),
|
||||
nodeId: text('node_id').notNull(),
|
||||
staffId: text('staff_id').notNull(),
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { processPackageSchema } from '@contracts';
|
||||
import type { Db } from '@shared/db.ts';
|
||||
import type { ProcessStore, WorkflowRecord } from '../../port.ts';
|
||||
import type { NodeInstance } from '../domain/owner-resolution.ts';
|
||||
import { nodeInstanceTable, processPackageTable, workflowTable } from './process-schema.ts';
|
||||
import { nodeInstanceTable, processPackageTable, workflowTable, nodeOwnerTable } from './process-schema.ts';
|
||||
|
||||
type WorkflowRow = typeof workflowTable.$inferSelect;
|
||||
|
||||
@@ -195,6 +195,16 @@ export function createProcessStore(db: Db): ProcessStore {
|
||||
);
|
||||
},
|
||||
|
||||
async nodeOwners(packageId) {
|
||||
const rows = await db.select().from(nodeOwnerTable).where(eq(nodeOwnerTable.packageId, packageId));
|
||||
return Object.fromEntries(rows.map((r) => [r.nodeId, r.staffId]));
|
||||
},
|
||||
async setNodeOwner(packageId, nodeId, staffId) {
|
||||
// 一节点至多一位运营负责人:先清后写;undefined = 撤掉,回到轮值
|
||||
await db.delete(nodeOwnerTable).where(and(eq(nodeOwnerTable.packageId, packageId), eq(nodeOwnerTable.nodeId, nodeId)));
|
||||
if (staffId !== undefined) await db.insert(nodeOwnerTable).values({ packageId, nodeId, staffId });
|
||||
},
|
||||
|
||||
async findInstanceByTask(taskId) {
|
||||
const rows = await db
|
||||
.select()
|
||||
|
||||
@@ -189,6 +189,12 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
|
||||
const binding =
|
||||
input.source.by === undefined ? undefined : await deps.org.workGroupBinding(input.source.by);
|
||||
|
||||
/*
|
||||
* 负责人合并(2026-07-30):运营中心 · 工作流里配的「节点负责人」垫在显式指派之下。
|
||||
* 显式指派赢 —— 测试台点名是这一单的意图;运营配置是常态,轮值是兜底。
|
||||
*/
|
||||
const opsOwners = await deps.store.nodeOwners(input.packageId);
|
||||
const assignments: Record<string, string> = { ...opsOwners, ...input.assignments };
|
||||
const record: WorkflowRecord = {
|
||||
id: deps.ids.next(),
|
||||
packageId: pkg.id,
|
||||
@@ -201,8 +207,8 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
|
||||
vars: {},
|
||||
startedAt: deps.clock.now().toISOString(),
|
||||
...(binding === undefined ? {} : { workGroup: { ...binding } }),
|
||||
// 节点指派:起单时定死某几个节点归谁(测试台定向);不传即照旧轮值
|
||||
...(input.assignments === undefined ? {} : { assignments: { ...input.assignments } }),
|
||||
// 节点指派三层:显式指派(测试台定向)> 运营配置的节点负责人 > 轮值
|
||||
...(Object.keys(assignments).length === 0 ? {} : { assignments }),
|
||||
};
|
||||
await deps.store.insertWorkflow(record);
|
||||
await deps.events.publish({ workflowId: record.id, kind: 'workflow-started', detail: { source: input.source } });
|
||||
@@ -223,6 +229,13 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
|
||||
return deps.store.listInstances(workflowId);
|
||||
},
|
||||
|
||||
async nodeOwners(packageId) {
|
||||
return deps.store.nodeOwners(packageId);
|
||||
},
|
||||
async setNodeOwner(packageId, nodeId, staffId) {
|
||||
await deps.store.setNodeOwner(packageId, nodeId, staffId);
|
||||
},
|
||||
|
||||
/*
|
||||
* 执行面五方法 —— 全部委托 FlowRuntime(M5-1 切面):
|
||||
* 语义、留痕与副作用编排住在运行时实现里,薄壳只转发。
|
||||
|
||||
Reference in New Issue
Block a user