forked from ChaiHao/BPOS-MVP
你存不了图的根因:保存跑闭合校验,而画图必然经过不完整的中间态
(节点没接完边、活动没安装)—— 画到一半就 412 卡死。实测同一份图:
改前 412 拒存;改后存为未生效的新版,报告随行。
## 模型改成三段
画图 = 只创建节点和边,图上真正生效可编辑的只有**名字**(双击不再出岗位一栏;
边上的字是辅助阅读的注记;岗位/判据/映射这些**闭环要素在节点属性里决定**;
分配到人是运营的事,设计侧不碰)
保存 = 不检查、不生效:恒存为新版本号,谁也伤不着 —— 因此不设确认卡,直接存;
任何版本都可编辑(在旧版上改 = 以旧版为底另起一版)
激活 = 完整性闸:不闭合 412 拦下并点名缺什么;过了即独占生效
## 异常看板(左,与版本卡同列同风)
画图不拦,看板替你盯着:逐条列这一版缺什么,**点一条定位到那个节点的属性**;
零异常时给空占位说「可以激活」。版本行的「激活」按钮按闭合灰显 ——
不闭合点不了,亮了才能上线。实测:v4 两条异常全列出、激活灰;闭合的 v2/v1 亮。
## 顺手修一个画布盲区
packageToGraph 以手绘存证为呈现真相,**语义里有、存证里没有的节点画布看不见**:
异常看板指着一个图上不存在的节点,点了也定位不到(实测:API 直存的包新增节点后
picker 里根本没有它)。补画:缺席节点按 60px 一档排在图纸下缘 —— 位置丑没关系,
看得见才能修。实测点异常 → 选中 A8 → 五张属性小卡弹出。
## 布局照你定的
row2 = 异常看板(1/3,与版本卡对齐)+ 节点属性卡(2/3,与流程图同宽);
属性卡内部小卡**等高 250px**,输入/输出超高的内容自己滚(实测五张全 250)。
种子改为保存后显式激活;NotClosedError 文案「不得保存」→「不得激活」。
typecheck ✓ lint ✓ depcruise ✓ 498 测试 ✓
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
75 lines
4.3 KiB
TypeScript
75 lines
4.3 KiB
TypeScript
/**
|
|
* module: process —— 对外类型(编码规范 §10.1;语义出处 OS 03)。
|
|
*
|
|
* 流程层:流程包定义与版本、**边映射闭合校验**、工作流实例
|
|
* (网关 / 审批点 / 失败走向 / 负责人四策略 / 聚合)。
|
|
*
|
|
* M5 进行中:domain 已落(闭合校验 / 四策略解析 / 状态聚合),
|
|
* 用例与 Mastra 工作流编译随后(编码规范 §6.1)。
|
|
*/
|
|
import type { ClosureReport, ClosureViolation, OwnerSpec, ProcessNode, ProcessPackage, TerminalState, WorkflowState } from '@contracts';
|
|
import type { ActivityContractFace, ClosureInput } from './internal/domain/closure.ts';
|
|
import type { Candidate, NodeInstance, ResolveContext, Resolution } from './internal/domain/owner-resolution.ts';
|
|
import type { AssembleScope, Dispatch, RejectRoute } from './internal/domain/routing.ts';
|
|
import type { WorkflowRecord } from './port.ts';
|
|
export type { ActivityContractFace, AssembleScope, Candidate, ClosureInput, Dispatch, RejectRoute, WorkflowRecord, ClosureReport, ClosureViolation, NodeInstance, OwnerSpec, ProcessPackage, ProcessNode, ResolveContext, Resolution, TerminalState, WorkflowState, };
|
|
/** 起单入参 —— 触发器与上游系统一律经**调用接口**调起(OS 03) */
|
|
export type StartWorkflowInput = {
|
|
readonly packageId: string;
|
|
readonly packageVersion?: number;
|
|
readonly subject: string;
|
|
/** 凡单必有来源:触发器即来源;纪要登记起单的,登记人即来源 */
|
|
readonly source: {
|
|
kind: 'periodic' | 'event' | 'registration';
|
|
ref: string;
|
|
by?: string;
|
|
};
|
|
readonly data: Record<string, unknown>;
|
|
};
|
|
export type ProcessService = {
|
|
/**
|
|
* 保存 —— **不检查、不生效**:恒存为新版本号(服务端算,不采信入参的 version),
|
|
* 状态为未生效。画图的中间态天然不完整,保存不设闸;完整性闸在 `setPackageState` 激活时。
|
|
*/
|
|
savePackage(pkg: ProcessPackage): Promise<ProcessPackage>;
|
|
listPackages(): Promise<ProcessPackage[]>;
|
|
getPackage(id: string, version?: number): Promise<ProcessPackage | undefined>;
|
|
/**
|
|
* 版本启停 —— 运行时开关,**不改定义一个字**。
|
|
* 激活先过闭合校验(不闭合抛 `NotClosedError`),过了即独占:同流其余版本一并停用;
|
|
* 停用生效版:整条流下线,新起单被拒。在途按启动时钉住的版本跑完,启停零影响。
|
|
*/
|
|
setPackageState(id: string, version: number, state: 'active' | 'retired'): Promise<ProcessPackage[]>;
|
|
/** 画图即校验 —— 与前端画布同源;只算不存 */
|
|
validate(pkg: ProcessPackage): Promise<ClosureReport>;
|
|
start(input: StartWorkflowInput): Promise<WorkflowRecord>;
|
|
getWorkflow(id: string): Promise<WorkflowRecord>;
|
|
listWorkflows(limit?: number): Promise<WorkflowRecord[]>;
|
|
instancesOf(workflowId: string): Promise<NodeInstance[]>;
|
|
/** 任务终态到达:完成即继续流转;非完成即走声明的失败走向 */
|
|
onTaskTerminal(taskId: string, terminal: TerminalState, ports: Record<string, Record<string, unknown>>): Promise<WorkflowState | undefined>;
|
|
applyFailure(workflowId: string, nodeId: string, instance: number, node: Extract<ProcessNode, {
|
|
kind: 'activity';
|
|
}>, terminal: TerminalState): Promise<WorkflowState>;
|
|
/** 审批裁决:通过继续;驳回按声明走向回指定节点**重开新实例** */
|
|
onApprovalVerdict(workflowId: string, nodeId: string, verdict: 'pass' | 'reject', payload: Record<string, unknown>): Promise<WorkflowState>;
|
|
/** 状态是聚合投影 —— 每次变动后重算 */
|
|
refreshState(workflowId: string): Promise<WorkflowState>;
|
|
terminate(workflowId: string, reason: string): Promise<WorkflowState>;
|
|
};
|
|
/** 起单入参不合调用接口契约 —— 在建工作流之前判掉,不留半条在途单 */
|
|
export declare class CallInterfaceViolationError extends Error {
|
|
readonly packageId: string;
|
|
readonly errors: readonly string[];
|
|
constructor(packageId: string, errors: readonly string[]);
|
|
}
|
|
/** 不闭合不得**激活** —— 画图与保存不拦(中间态天然不完整),上线这一步拦 */
|
|
export declare class NotClosedError extends Error {
|
|
readonly report: ClosureReport;
|
|
constructor(report: ClosureReport);
|
|
}
|
|
export declare class PositionVacantError extends Error {
|
|
readonly position: string;
|
|
constructor(position: string);
|
|
}
|