基线:鉴权会话+工作组+工作台三区修复+编排工作区+产品设计文档

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-27 20:43:35 +08:00
co-authored by Claude Fable 5
parent 61fe181678
commit ac85ecf2ea
119 changed files with 16634 additions and 1246 deletions
@@ -46,6 +46,20 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
return pkg;
}
/**
* 起单取版:未指定版本时取**最新已发布** —— 草稿不入选。
* 若按「最新版本」取,治理台一存草稿,起单立刻被没发布的定义顶住;
* 草稿是编排中的半成品,运行时永远不该看见它。
*/
async function latestPublished(id: string): Promise<ProcessPackage> {
const all = await deps.store.listPackages();
const hit = all
.filter((pkg) => pkg.id === id && pkg.state === 'published')
.sort((a, b) => b.version - a.version)[0];
if (hit === undefined) throw new Error(`流程包无已发布版本,不得起单:${id}`);
return hit;
}
async function requireWorkflow(id: string): Promise<WorkflowRecord> {
const record = await deps.store.getWorkflow(id);
if (record === undefined) throw new Error(`工作流不存在:${id}`);
@@ -104,7 +118,13 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
// 负责人 / 审批人:编排声明岗位,起单时确定性解析;结果与依据留痕
const spec = dispatch.kind === 'start-task' ? dispatch.node.owner : dispatch.node.approver;
const candidates = await deps.org.candidates(spec.position);
const resolution = resolveOwner(spec, { candidates });
/*
* 绑定先于策略:工作组说了这个岗位是谁,策略就无从发问(ADR-011)。
* 在岗与否在这里核 —— 绑定是**跨岗位**指派,拿该岗位的候选集核不了它。
*/
const boundTo = record.workGroup?.[spec.position];
const bound = boundTo !== undefined && (await deps.org.isActive(boundTo)) ? boundTo : undefined;
const resolution = resolveOwner(spec, { candidates, ...(bound === undefined ? {} : { bound }) });
if (resolution.kind === 'vacant') throw new PositionVacantError(spec.position);
await deps.events.publish({
@@ -246,7 +266,10 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
/** 起单:经调用接口调起流程包,生成一条工作流 */
async start(input: StartWorkflowInput) {
const pkg = await requirePackage(input.packageId, input.packageVersion);
const pkg =
input.packageVersion === undefined
? await latestPublished(input.packageId)
: await requirePackage(input.packageId, input.packageVersion);
if (pkg.state !== 'published') throw new Error(`流程包未发布,不得起单:${pkg.id}@${pkg.version}`);
// 先校验后建单:不合调用接口契约的输入**一条工作流都不留**。
@@ -254,6 +277,10 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
const check = deps.schemas.validate(pkg.callInterface.input, input.data);
if (!check.ok) throw new CallInterfaceViolationError(pkg.id, check.errors);
// 工作组按**来源人**装载(ADR-011):无来源人、或此人没建组 → 不装载,全走四策略
const binding =
input.source.by === undefined ? undefined : await deps.org.workGroupBinding(input.source.by);
const record: WorkflowRecord = {
id: deps.ids.next(),
packageId: pkg.id,
@@ -265,6 +292,7 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
ports: {},
vars: {},
startedAt: deps.clock.now().toISOString(),
...(binding === undefined ? {} : { workGroup: { ...binding } }),
};
await deps.store.insertWorkflow(record);
await deps.events.publish({ workflowId: record.id, kind: 'workflow-started', detail: { source: input.source } });