diff --git a/api-report/apps/api/src/modules/process/contract.d.ts b/api-report/apps/api/src/modules/process/contract.d.ts index 5239bdc..df57c52 100644 --- a/api-report/apps/api/src/modules/process/contract.d.ts +++ b/api-report/apps/api/src/modules/process/contract.d.ts @@ -7,10 +7,48 @@ * M5 进行中:domain 已落(闭合校验 / 四策略解析 / 状态聚合), * 用例与 Mastra 工作流编译随后(编码规范 §6.1)。 */ -import type { ClosureReport, ClosureViolation, OwnerSpec, ProcessPackage, WorkflowState } from '@contracts'; +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'; -export type { ActivityContractFace, Candidate, ClosureInput, ClosureReport, ClosureViolation, NodeInstance, OwnerSpec, ProcessPackage, ResolveContext, Resolution, WorkflowState, }; +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; +}; +export type ProcessService = { + savePackage(pkg: ProcessPackage): Promise; + listPackages(): Promise; + getPackage(id: string, version?: number): Promise; + /** 画图即校验 —— 与前端画布同源 */ + validate(pkg: ProcessPackage): Promise; + /** 不闭合不得发布 */ + publish(id: string, version: number): Promise; + start(input: StartWorkflowInput): Promise; + getWorkflow(id: string): Promise; + listWorkflows(limit?: number): Promise; + instancesOf(workflowId: string): Promise; + /** 任务终态到达:完成即继续流转;非完成即走声明的失败走向 */ + onTaskTerminal(taskId: string, terminal: TerminalState, ports: Record>): Promise; + applyFailure(workflowId: string, nodeId: string, instance: number, node: Extract, terminal: TerminalState): Promise; + /** 审批裁决:通过继续;驳回按声明走向回指定节点**重开新实例** */ + onApprovalVerdict(workflowId: string, nodeId: string, verdict: 'pass' | 'reject', payload: Record): Promise; + /** 状态是聚合投影 —— 每次变动后重算 */ + refreshState(workflowId: string): Promise; + terminate(workflowId: string, reason: string): Promise; +}; export declare class NotPublishableError extends Error { readonly report: ClosureReport; constructor(report: ClosureReport); diff --git a/api-report/apps/api/src/modules/process/index.d.ts b/api-report/apps/api/src/modules/process/index.d.ts index a2750cf..120b990 100644 --- a/api-report/apps/api/src/modules/process/index.d.ts +++ b/api-report/apps/api/src/modules/process/index.d.ts @@ -1,7 +1,13 @@ /** * module: process —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。 */ -export type { ActivityContractFace, Candidate, ClosureInput, ClosureReport, ClosureViolation, NodeInstance, OwnerSpec, ProcessPackage, ResolveContext, Resolution, WorkflowState, } from './contract.ts'; +export type { ActivityContractFace, AssembleScope, Candidate, ClosureInput, ClosureReport, ClosureViolation, NodeInstance, OwnerSpec, ProcessPackage, ProcessService, ResolveContext, Resolution, StartWorkflowInput, WorkflowRecord, WorkflowState, } from './contract.ts'; export { NotPublishableError, PositionVacantError } from './contract.ts'; +export type { ActivityDirectory, ApprovalGateway, Clock, FlowEventPublisher, IdGenerator, OrgDirectory, ProcessStore, ReceiptNotifier, Sfeel, TaskGateway, } from './port.ts'; +export type { ProcessServiceDeps } from './internal/usecase/process-service.ts'; +export { createProcessService } from './internal/usecase/process-service.ts'; +export { assemble, dispatchOf, joinReady, rejectRouteOf, selectEdges, startNodeOf, } from './internal/domain/routing.ts'; export { checkClosure, publishable } from './internal/domain/closure.ts'; export { aggregateWorkflowState, FAILURE_DIGESTIONS, isDigested, nextInstanceNumber, resolveOwner, } from './internal/domain/owner-resolution.ts'; +export { createProcessStore } from './internal/adapter/process-store.ts'; +export { nodeInstanceTable, processPackageTable, workflowTable } from './internal/adapter/process-schema.ts'; diff --git a/api-report/apps/api/src/modules/process/port.d.ts b/api-report/apps/api/src/modules/process/port.d.ts new file mode 100644 index 0000000..7ef7014 --- /dev/null +++ b/api-report/apps/api/src/modules/process/port.d.ts @@ -0,0 +1,120 @@ +/** + * module: process —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。 + */ +import type { ProcessPackage, StaffId, TerminalState, WorkflowState } from '@contracts'; +import type { NodeInstance } from './internal/domain/owner-resolution.ts'; +import type { ActivityContractFace } from './internal/domain/closure.ts'; +export type Clock = { + now(): Date; +}; +export type IdGenerator = { + next(): string; +}; +/** 工作流实例的持久面 —— 崩溃后可续跑 */ +export type WorkflowRecord = { + readonly id: string; + readonly packageId: string; + readonly packageVersion: number; + readonly subject: string; + readonly state: WorkflowState; + /** 来源:凡单必有来源(触发器 / 登记人 / 上游系统) */ + readonly source: { + kind: 'periodic' | 'event' | 'registration'; + ref: string; + by?: string; + }; + readonly initiation: Record; + /** 各节点输出口载荷 —— 边映射装配的来源 */ + readonly ports: Record>; + readonly vars: Record; + readonly startedAt: string; + readonly finishedAt?: string; +}; +export type ProcessStore = { + savePackage(pkg: ProcessPackage): Promise; + getPackage(id: string, version?: number): Promise; + listPackages(): Promise; + publishPackage(id: string, version: number): Promise; + insertWorkflow(record: WorkflowRecord): Promise; + getWorkflow(id: string): Promise; + listWorkflows(limit: number): Promise; + updateWorkflow(id: string, patch: Partial): Promise; + listInstances(workflowId: string): Promise; + insertInstance(workflowId: string, instance: NodeInstance & { + taskId?: string; + approvalId?: string; + }): Promise; + updateInstance(workflowId: string, nodeId: string, instance: number, patch: Partial & { + taskId?: string; + }): Promise; + findInstanceByTask(taskId: string): Promise<{ + workflowId: string; + nodeId: string; + instance: number; + } | undefined>; +}; +/** 活动契约面 —— app 注入 `activity-registry`(L4):编排校验与起单都要它 */ +export type ActivityDirectory = { + faces(): Promise>; +}; +/** 岗位与在岗人员 —— app 注入 `org`(L1):负责人四策略解析的候选集 */ +export type OrgDirectory = { + positions(): Promise>; + candidates(position: string): Promise>; +}; +/** 起单与终态 —— app 注入 `task`(L6) */ +export type TaskGateway = { + start(input: { + activityId: string; + ownerId: StaffId; + title: string; + workflowId: string; + nodeId: string; + data: Record; + message?: string; + }): Promise<{ + id: string; + }>; +}; +/** 审批点 —— app 注入 `human-tool`(L3):审批单卡到审批人工位 */ +export type ApprovalGateway = { + raise(input: { + workflowId: string; + nodeId: string; + approverId: StaffId; + subject: string; + basis: ReadonlyArray<{ + k: string; + v: string; + }>; + }): Promise<{ + id: string; + }>; +}; +/** S-FEEL —— app 注入 `@sfeel`:流转零 LLM */ +export type Sfeel = { + condition(expression: string, scope: Readonly>): boolean; + expression(expression: string, scope: Readonly>): unknown; +}; +/** 流转记入事件 —— app 注入 `event`(L1) */ +export type FlowEventPublisher = { + publish(input: { + workflowId: string; + kind: string; + detail: Record; + }): Promise; +}; +/** 终态回执 —— 登记人收消息 */ +export type ReceiptNotifier = { + notify(input: { + to: StaffId; + workflowId: string; + subject: string; + state: WorkflowState; + }): Promise; +}; +export type { TerminalState }; diff --git a/apps/api/src/modules/process/CONTRACT.md b/apps/api/src/modules/process/CONTRACT.md index 62e9f85..7a2d678 100644 --- a/apps/api/src/modules/process/CONTRACT.md +++ b/apps/api/src/modules/process/CONTRACT.md @@ -6,11 +6,16 @@ 画布的绘制(见 apps/web 的 gov-process) 公开出口: index.ts - - 类型:`ClosureInput` `ClosureReport` `ClosureViolation` `ActivityContractFace` - `Candidate` `Resolution` `NodeInstance` `WorkflowState` + - 类型:`ProcessService` `StartWorkflowInput` `WorkflowRecord` `ClosureReport` `ClosureViolation` + `ActivityContractFace` `Candidate` `Resolution` `NodeInstance` `WorkflowState` `Dispatch` - 错误:`NotPublishableError` `PositionVacantError` + - 端口类型:`ProcessStore` `ActivityDirectory` `OrgDirectory` `TaskGateway` `ApprovalGateway` + `Sfeel` `FlowEventPublisher` `ReceiptNotifier` + - 用例工厂:`createProcessService(deps): ProcessService` - 纯函数:`checkClosure` `publishable` `resolveOwner` `aggregateWorkflowState` - `isDigested` `nextInstanceNumber` + `isDigested` `nextInstanceNumber` `selectEdges` `assemble` `joinReady` + `rejectRouteOf` `dispatchOf` `startNodeOf` + - adapter:`createProcessStore`;Drizzle 表:`processPackageTable` `workflowTable` `nodeInstanceTable` 层级: L7(编码规范 §4) 依赖: @contracts;task(L6)、@sfeel @@ -26,6 +31,17 @@ - **失败走向的消化**:改道 / 容忍 / 升审批一经生效即消化;终止流待人工处置中算异常 - **流级重做一律重开新实例** —— 同节点、新任务 ID、留痕关联,不复活旧任务 -## M5 待补(docs/PROGRESS.md) -port.ts 与 usecase:流程包的存取与发布、工作流实例的起单与流转 -(Mastra `createWorkflow` 编译在 `internal/adapter/workflow-compiler.ts`,编码规范 §6.1)。 +其它不变量: + - **流转零 LLM**:选边与条件求值全部经 `@sfeel`;判定依据入事件记录 + - **XOR 恰取一条**:条件命中优先,无条件的边作默认分支兜底 + - **AND 汇合要回齐**:未回齐就不进汇合节点;容忍分支以「已消化」计,不阻汇合 + - **流程层不自动重试活动**(过程内重试归 Harness); + 流级重做一律人裁决后**重开新任务实例** + - **驳回走向声明式**:审批点的驳回按 `reject` 声明回指定节点重开新实例,不由临场决定 + - **凡单必有来源**:`source` 必填;终点按输出声明交付、按来源回执 + +## M5 待补(docs/PROGRESS.md · docs/debt.md M5-1) +`internal/adapter/workflow-compiler.ts` —— 编码规范 §6.1 规定流程包动态编译为 Mastra +`createWorkflow`/`createStep`(XOR → `.branch()`,AND → `.parallel()`,节点 `suspend()`/`resume()`)。 +当前流转由本模块自持的**确定性状态机 + `sys.node_instance` 持久**承担,语义等价且已可测; +Mastra 编译器作为可替换的 adapter 后补,替换时 usecase 不动。 diff --git a/apps/api/src/modules/process/contract.ts b/apps/api/src/modules/process/contract.ts index 27a80aa..7cc33f1 100644 --- a/apps/api/src/modules/process/contract.ts +++ b/apps/api/src/modules/process/contract.ts @@ -7,24 +7,89 @@ * M5 进行中:domain 已落(闭合校验 / 四策略解析 / 状态聚合), * 用例与 Mastra 工作流编译随后(编码规范 §6.1)。 */ -import type { ClosureReport, ClosureViolation, OwnerSpec, ProcessPackage, WorkflowState } from '@contracts'; +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; +}; + +export type ProcessService = { + savePackage(pkg: ProcessPackage): Promise; + listPackages(): Promise; + getPackage(id: string, version?: number): Promise; + /** 画图即校验 —— 与前端画布同源 */ + validate(pkg: ProcessPackage): Promise; + /** 不闭合不得发布 */ + publish(id: string, version: number): Promise; + + start(input: StartWorkflowInput): Promise; + getWorkflow(id: string): Promise; + listWorkflows(limit?: number): Promise; + instancesOf(workflowId: string): Promise; + + /** 任务终态到达:完成即继续流转;非完成即走声明的失败走向 */ + onTaskTerminal( + taskId: string, + terminal: TerminalState, + ports: Record>, + ): Promise; + applyFailure( + workflowId: string, + nodeId: string, + instance: number, + node: Extract, + terminal: TerminalState, + ): Promise; + /** 审批裁决:通过继续;驳回按声明走向回指定节点**重开新实例** */ + onApprovalVerdict( + workflowId: string, + nodeId: string, + verdict: 'pass' | 'reject', + payload: Record, + ): Promise; + /** 状态是聚合投影 —— 每次变动后重算 */ + refreshState(workflowId: string): Promise; + terminate(workflowId: string, reason: string): Promise; +}; + export class NotPublishableError extends Error { constructor(readonly report: ClosureReport) { super(`流程包不闭合,不得发布:${report.violations.map((v) => v.message).join('; ')}`); diff --git a/apps/api/src/modules/process/index.ts b/apps/api/src/modules/process/index.ts index fc395b8..ed6d63e 100644 --- a/apps/api/src/modules/process/index.ts +++ b/apps/api/src/modules/process/index.ts @@ -3,6 +3,7 @@ */ export type { ActivityContractFace, + AssembleScope, Candidate, ClosureInput, ClosureReport, @@ -10,12 +11,40 @@ export type { NodeInstance, OwnerSpec, ProcessPackage, + ProcessService, ResolveContext, Resolution, + StartWorkflowInput, + WorkflowRecord, WorkflowState, } from './contract.ts'; export { NotPublishableError, PositionVacantError } from './contract.ts'; +export type { + ActivityDirectory, + ApprovalGateway, + Clock, + FlowEventPublisher, + IdGenerator, + OrgDirectory, + ProcessStore, + ReceiptNotifier, + Sfeel, + TaskGateway, +} from './port.ts'; + +export type { ProcessServiceDeps } from './internal/usecase/process-service.ts'; +export { createProcessService } from './internal/usecase/process-service.ts'; + +export { + assemble, + dispatchOf, + joinReady, + rejectRouteOf, + selectEdges, + startNodeOf, +} from './internal/domain/routing.ts'; + export { checkClosure, publishable } from './internal/domain/closure.ts'; export { aggregateWorkflowState, @@ -24,3 +53,6 @@ export { nextInstanceNumber, resolveOwner, } from './internal/domain/owner-resolution.ts'; + +export { createProcessStore } from './internal/adapter/process-store.ts'; +export { nodeInstanceTable, processPackageTable, workflowTable } from './internal/adapter/process-schema.ts'; diff --git a/apps/api/src/modules/process/internal/adapter/process-schema.ts b/apps/api/src/modules/process/internal/adapter/process-schema.ts new file mode 100644 index 0000000..9bac72b --- /dev/null +++ b/apps/api/src/modules/process/internal/adapter/process-schema.ts @@ -0,0 +1,60 @@ +/** + * process 的 sys schema 表(编码规范 §7 / ADR-002)。 + * 流程包定义与版本 + 工作流实例 + 节点实例(重开产生新实例,旧实例不再入算)。 + */ +import { boolean, index, integer, jsonb, primaryKey, text, timestamp } from 'drizzle-orm/pg-core'; +import { sys } from '@shared/db.ts'; + +export const processPackageTable = sys.table( + 'process_package', + { + id: text('id').notNull(), + version: integer('version').notNull(), + name: text('name').notNull(), + state: text('state').notNull().default('draft'), + definition: jsonb('definition').notNull(), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + publishedAt: timestamp('published_at', { withTimezone: true }), + }, + (t) => [primaryKey({ columns: [t.id, t.version] }), index('process_package_state_idx').on(t.state)], +); + +export const workflowTable = sys.table( + 'workflow', + { + id: text('id').primaryKey(), + packageId: text('package_id').notNull(), + packageVersion: integer('package_version').notNull(), + subject: text('subject').notNull(), + state: text('state').notNull().default('running'), + /** 凡单必有来源 */ + source: jsonb('source').notNull(), + initiation: jsonb('initiation').notNull(), + ports: jsonb('ports').notNull().default({}), + vars: jsonb('vars').notNull().default({}), + startedAt: timestamp('started_at', { withTimezone: true }).notNull().defaultNow(), + finishedAt: timestamp('finished_at', { withTimezone: true }), + }, + (t) => [index('workflow_state_idx').on(t.state, t.startedAt)], +); + +export const nodeInstanceTable = sys.table( + 'node_instance', + { + workflowId: text('workflow_id').notNull(), + nodeId: text('node_id').notNull(), + instance: integer('instance').notNull(), + running: boolean('running').notNull().default(false), + waiting: boolean('waiting').notNull().default(false), + activated: boolean('activated').notNull().default(true), + terminal: text('terminal'), + failureDigested: boolean('failure_digested'), + taskId: text('task_id'), + approvalId: text('approval_id'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + }, + (t) => [ + primaryKey({ columns: [t.workflowId, t.nodeId, t.instance] }), + index('node_instance_task_idx').on(t.taskId), + ], +); diff --git a/apps/api/src/modules/process/internal/adapter/process-store.ts b/apps/api/src/modules/process/internal/adapter/process-store.ts new file mode 100644 index 0000000..65ea1dc --- /dev/null +++ b/apps/api/src/modules/process/internal/adapter/process-store.ts @@ -0,0 +1,174 @@ +/** + * process 存储 adapter —— Drizzle 只准出现在 adapter(编码规范 §5.3)。 + */ +import { and, asc, desc, eq } from 'drizzle-orm'; +import type { ProcessPackage, TerminalState, WorkflowState } from '@contracts'; +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'; + +type WorkflowRow = typeof workflowTable.$inferSelect; + +function toWorkflow(row: WorkflowRow): WorkflowRecord { + return { + id: row.id, + packageId: row.packageId, + packageVersion: row.packageVersion, + subject: row.subject, + state: row.state as WorkflowState, + source: row.source as WorkflowRecord['source'], + initiation: row.initiation as Record, + ports: row.ports as Record>, + vars: row.vars as Record, + startedAt: row.startedAt.toISOString(), + ...(row.finishedAt === null ? {} : { finishedAt: row.finishedAt.toISOString() }), + }; +} + +export function createProcessStore(db: Db): ProcessStore { + return { + async savePackage(pkg: ProcessPackage) { + await db + .insert(processPackageTable) + .values({ id: pkg.id, version: pkg.version, name: pkg.name, state: pkg.state, definition: pkg }) + .onConflictDoUpdate({ + target: [processPackageTable.id, processPackageTable.version], + set: { name: pkg.name, state: pkg.state, definition: pkg }, + }); + }, + + async getPackage(id, version) { + const conditions = [eq(processPackageTable.id, id)]; + if (version !== undefined) conditions.push(eq(processPackageTable.version, version)); + const rows = await db + .select() + .from(processPackageTable) + .where(and(...conditions)) + .orderBy(desc(processPackageTable.version)) + .limit(1); + return rows[0] === undefined ? undefined : processPackageSchema.parse(rows[0].definition); + }, + + async listPackages() { + const rows = await db.select().from(processPackageTable).orderBy(asc(processPackageTable.name)); + return rows.map((r) => processPackageSchema.parse(r.definition)); + }, + + async publishPackage(id, version) { + const rows = await db + .select() + .from(processPackageTable) + .where(and(eq(processPackageTable.id, id), eq(processPackageTable.version, version))) + .limit(1); + const row = rows[0]; + if (row === undefined) return; + const definition = { ...(row.definition as Record), state: 'published' }; + await db + .update(processPackageTable) + .set({ state: 'published', publishedAt: new Date(), definition }) + .where(and(eq(processPackageTable.id, id), eq(processPackageTable.version, version))); + }, + + async insertWorkflow(record) { + await db.insert(workflowTable).values({ + id: record.id, + packageId: record.packageId, + packageVersion: record.packageVersion, + subject: record.subject, + state: record.state, + source: record.source, + initiation: record.initiation, + ports: record.ports, + vars: record.vars, + startedAt: new Date(record.startedAt), + }); + }, + + async getWorkflow(id) { + const rows = await db.select().from(workflowTable).where(eq(workflowTable.id, id)).limit(1); + return rows[0] === undefined ? undefined : toWorkflow(rows[0]); + }, + + async listWorkflows(limit) { + const rows = await db.select().from(workflowTable).orderBy(desc(workflowTable.startedAt)).limit(limit); + return rows.map(toWorkflow); + }, + + async updateWorkflow(id, patch) { + const values: Record = {}; + if (patch.state !== undefined) values['state'] = patch.state; + if (patch.ports !== undefined) values['ports'] = patch.ports; + if (patch.vars !== undefined) values['vars'] = patch.vars; + if (patch.finishedAt !== undefined) values['finishedAt'] = new Date(patch.finishedAt); + const [row] = await db.update(workflowTable).set(values).where(eq(workflowTable.id, id)).returning(); + if (row === undefined) throw new Error(`工作流不存在:${id}`); + return toWorkflow(row); + }, + + async listInstances(workflowId) { + const rows = await db + .select() + .from(nodeInstanceTable) + .where(eq(nodeInstanceTable.workflowId, workflowId)) + .orderBy(asc(nodeInstanceTable.createdAt)); + return rows.map( + (r): NodeInstance => ({ + nodeId: r.nodeId, + instance: r.instance, + running: r.running, + waiting: r.waiting, + activated: r.activated, + ...(r.terminal === null ? {} : { terminal: r.terminal as TerminalState }), + ...(r.failureDigested === null ? {} : { failureDigested: r.failureDigested }), + }), + ); + }, + + async insertInstance(workflowId, instance) { + await db.insert(nodeInstanceTable).values({ + workflowId, + nodeId: instance.nodeId, + instance: instance.instance, + running: instance.running, + waiting: instance.waiting, + activated: instance.activated, + terminal: instance.terminal ?? null, + failureDigested: instance.failureDigested ?? null, + taskId: instance.taskId ?? null, + approvalId: instance.approvalId ?? null, + }); + }, + + async updateInstance(workflowId, nodeId, instance, patch) { + const values: Record = {}; + if (patch.running !== undefined) values['running'] = patch.running; + if (patch.waiting !== undefined) values['waiting'] = patch.waiting; + if (patch.terminal !== undefined) values['terminal'] = patch.terminal; + if (patch.failureDigested !== undefined) values['failureDigested'] = patch.failureDigested; + if (patch.taskId !== undefined) values['taskId'] = patch.taskId; + if (Object.keys(values).length === 0) return; + await db + .update(nodeInstanceTable) + .set(values) + .where( + and( + eq(nodeInstanceTable.workflowId, workflowId), + eq(nodeInstanceTable.nodeId, nodeId), + eq(nodeInstanceTable.instance, instance), + ), + ); + }, + + async findInstanceByTask(taskId) { + const rows = await db + .select() + .from(nodeInstanceTable) + .where(eq(nodeInstanceTable.taskId, taskId)) + .limit(1); + const row = rows[0]; + return row === undefined ? undefined : { workflowId: row.workflowId, nodeId: row.nodeId, instance: row.instance }; + }, + }; +} diff --git a/apps/api/src/modules/process/internal/domain/routing.ts b/apps/api/src/modules/process/internal/domain/routing.ts new file mode 100644 index 0000000..f7ac808 --- /dev/null +++ b/apps/api/src/modules/process/internal/domain/routing.ts @@ -0,0 +1,135 @@ +/** + * 流程引擎的确定性流转 —— 纯逻辑,零 IO、**零 LLM**(编码规范 §5.3;语义出处 OS 04)。 + * + * 「流程引擎 —— 确定性流转:接收调用接口的起单、按结构调度节点、把审批点卡到人的工位、 + * 执行网关的分合、把每次流转记入事件。**零 LLM、零临场**。」 + */ +import type { Edge, ProcessNode, ProcessPackage } from '@contracts'; + +export type PredicateEvaluator = (expression: string, scope: Readonly>) => boolean; + +/** 一次流转要做的事 */ +export type Dispatch = + /** 起一单任务 */ + | { readonly kind: 'start-task'; readonly node: Extract; readonly payload: Record } + /** 把审批点卡到人的工位 */ + | { readonly kind: 'raise-approval'; readonly node: Extract; readonly payload: Record } + /** 流程包显式终点:按输出声明交付、按来源回执 */ + | { readonly kind: 'finish'; readonly node: Extract; readonly payload: Record }; + +/** 边映射装配:上游输出口载荷 / 流程变量 / 起单载荷 / 常量 → 下游 data 字段 */ +export type AssembleScope = { + readonly ports: Readonly>>; + readonly vars: Readonly>; + readonly initiation: Readonly>; +}; + +export function assemble( + edge: Edge, + scope: AssembleScope, + evaluateExpression: (sfeel: string, scope: Readonly>) => unknown, +): Record { + const out: Record = {}; + const flat: Record = { ...scope.initiation, ...scope.vars, ...scope.ports }; + for (const mapping of edge.mapping) { + switch (mapping.source.from) { + case 'output-port': + out[mapping.target] = pick(scope.ports[mapping.source.port], mapping.source.path); + break; + case 'workflow-var': + out[mapping.target] = scope.vars[mapping.source.name]; + break; + case 'initiation': + out[mapping.target] = pick(scope.initiation, mapping.source.path); + break; + case 'constant': + out[mapping.target] = mapping.source.value; + break; + case 'expression': + out[mapping.target] = evaluateExpression(mapping.source.sfeel, flat); + break; + } + } + return out; +} + +function pick(source: Readonly> | undefined, path: string): unknown { + if (source === undefined) return undefined; + return path.split('.').reduce((acc, seg) => { + if (acc === null || acc === undefined || typeof acc !== 'object') return undefined; + return (acc as Record)[seg]; + }, source); +} + +/** + * 从某节点出发,算出接下来该走的边。 + * XOR:条件求值,**恰取一条**;AND:全部出边同时走(分叉)。 + * 条件声明式、静态可检,**不得内嵌自由逻辑**。 + */ +export function selectEdges( + pkg: ProcessPackage, + fromNodeId: string, + scope: Readonly>, + evaluate: PredicateEvaluator, +): Edge[] { + const node = pkg.nodes.find((n) => n.id === fromNodeId); + const outgoing = pkg.edges.filter((e) => e.from === fromNodeId); + + if (node?.kind === 'gateway' && node.gateway === 'exclusive' && node.mode === 'fork') { + // 判定依据入事件记录;无条件的边作默认分支兜底 + const hit = outgoing.find((e) => e.condition !== undefined && evaluate(e.condition, scope)); + const fallback = outgoing.find((e) => e.condition === undefined); + const chosen = hit ?? fallback; + return chosen === undefined ? [] : [chosen]; + } + + // 并行网关分叉、活动节点、审批点:全部出边 + return outgoing; +} + +/** AND 汇合是否回齐:全部入边的来源分支都已收敛 */ +export function joinReady( + pkg: ProcessPackage, + joinNodeId: string, + converged: ReadonlySet, + tolerated: ReadonlySet, +): boolean { + const inbound = pkg.edges.filter((e) => e.to === joinNodeId); + // 容忍分支以「已消化」计,不阻汇合 + return inbound.every((e) => converged.has(e.from) || tolerated.has(e.from)); +} + +/** 节点该被怎么调度 */ +export function dispatchOf(node: ProcessNode, payload: Record): Dispatch | undefined { + if (node.kind === 'activity') return { kind: 'start-task', node, payload }; + if (node.kind === 'approval') return { kind: 'raise-approval', node, payload }; + if (node.kind === 'end') return { kind: 'finish', node, payload }; + return undefined; +} + +/** 审批点的驳回走向:声明式,**不由临场决定** */ +export type RejectRoute = + | { readonly kind: 'reopen'; readonly nodeId: string; readonly payload: Record } + | { readonly kind: 'terminate' }; + +export function rejectRouteOf( + node: Extract, + scope: AssembleScope, + evaluateExpression: (sfeel: string, scope: Readonly>) => unknown, +): RejectRoute { + if (node.reject.kind === 'terminate') return { kind: 'terminate' }; + return { + kind: 'reopen', + nodeId: node.reject.nodeId, + payload: assemble( + { id: `${node.id}-reject`, from: node.id, to: node.reject.nodeId, mapping: node.reject.mapping, reopen: true }, + scope, + evaluateExpression, + ), + }; +} + +/** 起点:找出流程包的入口节点 */ +export function startNodeOf(pkg: ProcessPackage): ProcessNode | undefined { + return pkg.nodes.find((n) => n.kind === 'start'); +} diff --git a/apps/api/src/modules/process/internal/usecase/process-service.ts b/apps/api/src/modules/process/internal/usecase/process-service.ts new file mode 100644 index 0000000..74cc3ff --- /dev/null +++ b/apps/api/src/modules/process/internal/usecase/process-service.ts @@ -0,0 +1,370 @@ +/** + * process 用例 —— 流程引擎:确定性流转,**零 LLM、零临场**(编码规范 §5.3;语义出处 OS 03 / 04)。 + * + * 起单 · 调度节点 · 审批点卡工位 · 网关分合 · 每次流转记入事件。 + */ +import type { ProcessNode, ProcessPackage, StaffId, TerminalState, WorkflowState } from '@contracts'; +import type { ProcessService, StartWorkflowInput } from '../../contract.ts'; +import { NotPublishableError, PositionVacantError } from '../../contract.ts'; +import type { + ActivityDirectory, + ApprovalGateway, + Clock, + FlowEventPublisher, + IdGenerator, + OrgDirectory, + ProcessStore, + ReceiptNotifier, + Sfeel, + TaskGateway, + WorkflowRecord, +} from '../../port.ts'; +import { checkClosure } from '../domain/closure.ts'; +import { aggregateWorkflowState, isDigested, nextInstanceNumber, resolveOwner } from '../domain/owner-resolution.ts'; +import type { NodeInstance } from '../domain/owner-resolution.ts'; +import { assemble, dispatchOf, joinReady, rejectRouteOf, selectEdges, startNodeOf } from '../domain/routing.ts'; + +export type ProcessServiceDeps = { + readonly store: ProcessStore; + readonly activities: ActivityDirectory; + readonly org: OrgDirectory; + readonly tasks: TaskGateway; + readonly approvals: ApprovalGateway; + readonly sfeel: Sfeel; + readonly events: FlowEventPublisher; + readonly receipts: ReceiptNotifier; + readonly clock: Clock; + readonly ids: IdGenerator; +}; + +export function createProcessService(deps: ProcessServiceDeps): ProcessService { + async function requirePackage(id: string, version?: number): Promise { + const pkg = await deps.store.getPackage(id, version); + if (pkg === undefined) throw new Error(`流程包不存在:${id}`); + return pkg; + } + + async function requireWorkflow(id: string): Promise { + const record = await deps.store.getWorkflow(id); + if (record === undefined) throw new Error(`工作流不存在:${id}`); + return record; + } + + function scopeOf(record: WorkflowRecord) { + return { ports: record.ports, vars: record.vars, initiation: record.initiation }; + } + + /** 起一个节点:活动 → 起单;审批点 → 卡工位;终点 → 收束 */ + async function enter( + record: WorkflowRecord, + pkg: ProcessPackage, + node: ProcessNode, + payload: Record, + ): Promise { + const dispatch = dispatchOf(node, payload); + + if (dispatch === undefined) { + // 网关本身不是责任单元:直接继续流转 + await advanceFrom(record, pkg, node.id, payload); + return; + } + + const instances = await deps.store.listInstances(record.id); + const instance = nextInstanceNumber(instances, node.id); + + if (dispatch.kind === 'finish') { + await deps.store.insertInstance(record.id, { + nodeId: node.id, + instance, + running: false, + waiting: false, + activated: true, + terminal: 'released', + }); + const finished = await deps.store.updateWorkflow(record.id, { + state: 'completed', + finishedAt: deps.clock.now().toISOString(), + ports: { ...record.ports, __output: payload }, + }); + await deps.events.publish({ workflowId: record.id, kind: 'workflow-completed', detail: payload }); + // 凡单必有来源 —— 登记人收消息回执 + if (record.source.by !== undefined) { + await deps.receipts.notify({ + to: record.source.by as StaffId, + workflowId: record.id, + subject: record.subject, + state: finished.state, + }); + } + return; + } + + // 负责人 / 审批人:编排声明岗位,起单时确定性解析;结果与依据留痕 + 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 }); + if (resolution.kind === 'vacant') throw new PositionVacantError(spec.position); + + await deps.events.publish({ + workflowId: record.id, + kind: 'owner-resolved', + detail: { nodeId: node.id, position: spec.position, staffId: resolution.staffId, by: resolution.by, rationale: resolution.rationale }, + }); + + if (dispatch.kind === 'start-task') { + const task = await deps.tasks.start({ + activityId: dispatch.node.activityId, + ownerId: resolution.staffId as StaffId, + title: dispatch.node.label, + workflowId: record.id, + nodeId: node.id, + data: payload, + ...(dispatch.node.message === undefined ? {} : { message: dispatch.node.message }), + }); + await deps.store.insertInstance(record.id, { + nodeId: node.id, + instance, + running: true, + waiting: false, + activated: true, + taskId: task.id, + }); + await deps.events.publish({ workflowId: record.id, kind: 'task-started', detail: { nodeId: node.id, taskId: task.id } }); + return; + } + + const approval = await deps.approvals.raise({ + workflowId: record.id, + nodeId: node.id, + approverId: resolution.staffId as StaffId, + subject: dispatch.node.label, + basis: Object.entries(payload).map(([k, v]) => ({ k, v: String(v) })), + }); + await deps.store.insertInstance(record.id, { + nodeId: node.id, + instance, + running: false, + waiting: true, + activated: true, + approvalId: approval.id, + }); + await deps.events.publish({ workflowId: record.id, kind: 'approval-raised', detail: { nodeId: node.id, approvalId: approval.id } }); + } + + /** 从某节点继续流转:选边 → 装配 → 进下一节点(AND 汇合要回齐) */ + async function advanceFrom( + record: WorkflowRecord, + pkg: ProcessPackage, + fromNodeId: string, + payload: Record, + ): Promise { + const scope = scopeOf(record); + const flat = { ...scope.initiation, ...scope.vars, ...payload }; + const edges = selectEdges(pkg, fromNodeId, flat, deps.sfeel.condition); + + for (const edge of edges) { + const target = pkg.nodes.find((n) => n.id === edge.to); + if (target === undefined) continue; + + if (target.kind === 'gateway' && target.mode === 'join') { + const instances = await deps.store.listInstances(record.id); + const converged = new Set(instances.filter((i) => i.terminal === 'released').map((i) => i.nodeId)); + const tolerated = new Set(instances.filter((i) => i.failureDigested === true).map((i) => i.nodeId)); + // 未回齐就等着 —— 别的分支到了会再来一次 + if (!joinReady(pkg, target.id, converged, tolerated)) continue; + } + + const assembled = assemble(edge, { ...scope, ports: { ...scope.ports, [fromNodeId]: payload } }, deps.sfeel.expression); + await enter(record, pkg, target, assembled); + } + } + + return { + async savePackage(pkg) { + await deps.store.savePackage(pkg); + return pkg; + }, + + async listPackages() { + return deps.store.listPackages(); + }, + + async getPackage(id, version) { + return deps.store.getPackage(id, version); + }, + + /** 画图即校验:发布前跑闭合校验 */ + async validate(pkg) { + return checkClosure({ + pkg, + contracts: await deps.activities.faces(), + positions: await deps.org.positions(), + }); + }, + + /** 不闭合不得发布 */ + async publish(id, version) { + const pkg = await requirePackage(id, version); + const report = await this.validate(pkg); + if (!report.publishable) throw new NotPublishableError(report); + await deps.store.publishPackage(id, version); + return report; + }, + + /** 起单:经调用接口调起流程包,生成一条工作流 */ + async start(input: StartWorkflowInput) { + const pkg = await requirePackage(input.packageId, input.packageVersion); + if (pkg.state !== 'published') throw new Error(`流程包未发布,不得起单:${pkg.id}@${pkg.version}`); + + const record: WorkflowRecord = { + id: deps.ids.next(), + packageId: pkg.id, + packageVersion: pkg.version, + subject: input.subject, + state: 'running', + source: input.source, + initiation: input.data, + ports: {}, + vars: {}, + startedAt: deps.clock.now().toISOString(), + }; + await deps.store.insertWorkflow(record); + await deps.events.publish({ workflowId: record.id, kind: 'workflow-started', detail: { source: input.source } }); + + const start = startNodeOf(pkg); + if (start !== undefined) await advanceFrom(record, pkg, start.id, input.data); + return record; + }, + + async getWorkflow(id) { + return requireWorkflow(id); + }, + + async listWorkflows(limit = 50) { + return deps.store.listWorkflows(limit); + }, + + async instancesOf(workflowId) { + return deps.store.listInstances(workflowId); + }, + + /** 任务终态到达:按终态决定继续流转还是走失败走向 */ + async onTaskTerminal(taskId, terminal: TerminalState, ports: Record>) { + const located = await deps.store.findInstanceByTask(taskId); + if (located === undefined) return undefined; + + const record = await requireWorkflow(located.workflowId); + const pkg = await requirePackage(record.packageId, record.packageVersion); + const node = pkg.nodes.find((n) => n.id === located.nodeId); + + await deps.store.updateInstance(record.id, located.nodeId, located.instance, { + running: false, + waiting: false, + terminal, + }); + await deps.events.publish({ workflowId: record.id, kind: 'task-terminal', detail: { taskId, terminal } }); + + const merged = await deps.store.updateWorkflow(record.id, { ports: { ...record.ports, ...ports } }); + + if (terminal === 'released') { + // 落地收敛后才发射输出口 —— 下游只见落地完成的任务 + const payload = Object.values(ports)[0] ?? {}; + await advanceFrom(merged, pkg, located.nodeId, payload); + } else if (node?.kind === 'activity') { + // 非完成终态触发**声明的失败走向**;流程层不自动重试 + await this.applyFailure(record.id, located.nodeId, located.instance, node, terminal); + } + + return this.refreshState(record.id); + }, + + /** 失败走向四选一:终止流(默认)/ 改道 / 升审批 / 容忍 */ + async applyFailure(workflowId, nodeId, instance, node, terminal) { + const record = await requireWorkflow(workflowId); + const pkg = await requirePackage(record.packageId, record.packageVersion); + const disposition = node.failure; + const digested = isDigested(disposition.kind); + + await deps.store.updateInstance(workflowId, nodeId, instance, { failureDigested: digested }); + await deps.events.publish({ + workflowId, + kind: 'failure-disposition', + detail: { nodeId, terminal, disposition: disposition.kind }, + }); + + if (disposition.kind === 'reroute') { + const edge = pkg.edges.find((e) => e.id === disposition.edgeId); + const target = edge === undefined ? undefined : pkg.nodes.find((n) => n.id === edge.to); + if (edge !== undefined && target !== undefined) { + // 失败载荷 = 终态、原因码与说明 + const payload = assemble(edge, scopeOf(record), deps.sfeel.expression); + await enter(record, pkg, target, { ...payload, __terminal: terminal }); + } + } + + if (disposition.kind === 'escalate') { + const target = pkg.nodes.find((n) => n.id === disposition.approvalNodeId); + if (target !== undefined) await enter(record, pkg, target, { __terminal: terminal, __from: nodeId }); + } + + return this.refreshState(workflowId); + }, + + /** 审批裁决:通过继续流转;驳回按**声明的走向**回指定节点重开新实例 */ + async onApprovalVerdict(workflowId, nodeId, verdict, payload) { + const record = await requireWorkflow(workflowId); + const pkg = await requirePackage(record.packageId, record.packageVersion); + const node = pkg.nodes.find((n) => n.id === nodeId); + if (node?.kind !== 'approval') throw new Error(`节点不是审批点:${nodeId}`); + + const instances = await deps.store.listInstances(workflowId); + const current = instances.filter((i) => i.nodeId === nodeId).at(-1); + if (current !== undefined) { + await deps.store.updateInstance(workflowId, nodeId, current.instance, { + waiting: false, + terminal: 'released', + }); + } + await deps.events.publish({ workflowId, kind: 'approval-verdict', detail: { nodeId, verdict } }); + + if (verdict === 'pass') { + await advanceFrom(record, pkg, nodeId, payload); + } else { + const route = rejectRouteOf(node, scopeOf(record), deps.sfeel.expression); + if (route.kind === 'terminate') { + await deps.store.updateWorkflow(workflowId, { state: 'terminated', finishedAt: deps.clock.now().toISOString() }); + } else { + // 回指定节点**重开新实例**(同节点、新任务 ID、留痕关联),不复活旧任务 + const target = pkg.nodes.find((n) => n.id === route.nodeId); + if (target !== undefined) await enter(record, pkg, target, { ...route.payload, ...payload }); + } + } + return this.refreshState(workflowId); + }, + + /** 工作流状态是聚合投影 —— 每次变动后重算 */ + async refreshState(workflowId): Promise { + const record = await requireWorkflow(workflowId); + if (record.state === 'terminated') return 'terminated'; + const instances = await deps.store.listInstances(workflowId); + const state = aggregateWorkflowState(instances as NodeInstance[], false); + if (state !== record.state) { + await deps.store.updateWorkflow(workflowId, { + state, + ...(state === 'completed' ? { finishedAt: deps.clock.now().toISOString() } : {}), + }); + } + return state; + }, + + async terminate(workflowId, reason) { + await deps.store.updateWorkflow(workflowId, { + state: 'terminated', + finishedAt: deps.clock.now().toISOString(), + }); + // 工作流终止级联:在途任务终止、待答卡与征答消息作废、审批点撤卡 + await deps.events.publish({ workflowId, kind: 'workflow-terminated', detail: { reason } }); + return 'terminated'; + }, + }; +} diff --git a/apps/api/src/modules/process/port.ts b/apps/api/src/modules/process/port.ts new file mode 100644 index 0000000..5c385ee --- /dev/null +++ b/apps/api/src/modules/process/port.ts @@ -0,0 +1,101 @@ +/** + * module: process —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。 + */ +import type { ProcessPackage, StaffId, TerminalState, WorkflowState } from '@contracts'; +import type { NodeInstance } from './internal/domain/owner-resolution.ts'; +import type { ActivityContractFace } from './internal/domain/closure.ts'; + +export type Clock = { now(): Date }; +export type IdGenerator = { next(): string }; + +/** 工作流实例的持久面 —— 崩溃后可续跑 */ +export type WorkflowRecord = { + readonly id: string; + readonly packageId: string; + readonly packageVersion: number; + readonly subject: string; + readonly state: WorkflowState; + /** 来源:凡单必有来源(触发器 / 登记人 / 上游系统) */ + readonly source: { kind: 'periodic' | 'event' | 'registration'; ref: string; by?: string }; + readonly initiation: Record; + /** 各节点输出口载荷 —— 边映射装配的来源 */ + readonly ports: Record>; + readonly vars: Record; + readonly startedAt: string; + readonly finishedAt?: string; +}; + +export type ProcessStore = { + savePackage(pkg: ProcessPackage): Promise; + getPackage(id: string, version?: number): Promise; + listPackages(): Promise; + publishPackage(id: string, version: number): Promise; + + insertWorkflow(record: WorkflowRecord): Promise; + getWorkflow(id: string): Promise; + listWorkflows(limit: number): Promise; + updateWorkflow(id: string, patch: Partial): Promise; + + listInstances(workflowId: string): Promise; + insertInstance(workflowId: string, instance: NodeInstance & { taskId?: string; approvalId?: string }): Promise; + updateInstance( + workflowId: string, + nodeId: string, + instance: number, + patch: Partial & { taskId?: string }, + ): Promise; + findInstanceByTask(taskId: string): Promise<{ workflowId: string; nodeId: string; instance: number } | undefined>; +}; + +/** 活动契约面 —— app 注入 `activity-registry`(L4):编排校验与起单都要它 */ +export type ActivityDirectory = { + faces(): Promise>; +}; + +/** 岗位与在岗人员 —— app 注入 `org`(L1):负责人四策略解析的候选集 */ +export type OrgDirectory = { + positions(): Promise>; + candidates(position: string): Promise>; +}; + +/** 起单与终态 —— app 注入 `task`(L6) */ +export type TaskGateway = { + start(input: { + activityId: string; + ownerId: StaffId; + title: string; + workflowId: string; + nodeId: string; + data: Record; + message?: string; + }): Promise<{ id: string }>; +}; + +/** 审批点 —— app 注入 `human-tool`(L3):审批单卡到审批人工位 */ +export type ApprovalGateway = { + raise(input: { + workflowId: string; + nodeId: string; + approverId: StaffId; + subject: string; + basis: ReadonlyArray<{ k: string; v: string }>; + }): Promise<{ id: string }>; +}; + +/** S-FEEL —— app 注入 `@sfeel`:流转零 LLM */ +export type Sfeel = { + condition(expression: string, scope: Readonly>): boolean; + expression(expression: string, scope: Readonly>): unknown; +}; + +/** 流转记入事件 —— app 注入 `event`(L1) */ +export type FlowEventPublisher = { + publish(input: { workflowId: string; kind: string; detail: Record }): Promise; +}; + +/** 终态回执 —— 登记人收消息 */ +export type ReceiptNotifier = { + notify(input: { to: StaffId; workflowId: string; subject: string; state: WorkflowState }): Promise; +}; + +export type { TerminalState }; diff --git a/db/migrations/0006_easy_enchantress.sql b/db/migrations/0006_easy_enchantress.sql new file mode 100644 index 0000000..decd07f --- /dev/null +++ b/db/migrations/0006_easy_enchantress.sql @@ -0,0 +1,43 @@ +CREATE TABLE "sys"."node_instance" ( + "workflow_id" text NOT NULL, + "node_id" text NOT NULL, + "instance" integer NOT NULL, + "running" boolean DEFAULT false NOT NULL, + "waiting" boolean DEFAULT false NOT NULL, + "activated" boolean DEFAULT true NOT NULL, + "terminal" text, + "failure_digested" boolean, + "task_id" text, + "approval_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "node_instance_workflow_id_node_id_instance_pk" PRIMARY KEY("workflow_id","node_id","instance") +); +--> statement-breakpoint +CREATE TABLE "sys"."process_package" ( + "id" text NOT NULL, + "version" integer NOT NULL, + "name" text NOT NULL, + "state" text DEFAULT 'draft' NOT NULL, + "definition" jsonb NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "published_at" timestamp with time zone, + CONSTRAINT "process_package_id_version_pk" PRIMARY KEY("id","version") +); +--> statement-breakpoint +CREATE TABLE "sys"."workflow" ( + "id" text PRIMARY KEY NOT NULL, + "package_id" text NOT NULL, + "package_version" integer NOT NULL, + "subject" text NOT NULL, + "state" text DEFAULT 'running' NOT NULL, + "source" jsonb NOT NULL, + "initiation" jsonb NOT NULL, + "ports" jsonb DEFAULT '{}'::jsonb NOT NULL, + "vars" jsonb DEFAULT '{}'::jsonb NOT NULL, + "started_at" timestamp with time zone DEFAULT now() NOT NULL, + "finished_at" timestamp with time zone +); +--> statement-breakpoint +CREATE INDEX "node_instance_task_idx" ON "sys"."node_instance" USING btree ("task_id");--> statement-breakpoint +CREATE INDEX "process_package_state_idx" ON "sys"."process_package" USING btree ("state");--> statement-breakpoint +CREATE INDEX "workflow_state_idx" ON "sys"."workflow" USING btree ("state","started_at"); \ No newline at end of file diff --git a/db/migrations/meta/0006_snapshot.json b/db/migrations/meta/0006_snapshot.json new file mode 100644 index 0000000..4e2648d --- /dev/null +++ b/db/migrations/meta/0006_snapshot.json @@ -0,0 +1,2894 @@ +{ + "id": "8ff12b4a-4df5-4281-9322-9118df22e5db", + "prevId": "d9cf0d23-00d3-4ced-9372-9f5e46edec64", + "version": "7", + "dialect": "postgresql", + "tables": { + "sys.activity_asset": { + "name": "activity_asset", + "schema": "sys", + "columns": { + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effective": { + "name": "effective", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_asset_effective_key": { + "name": "activity_asset_effective_key", + "columns": [ + { + "expression": "activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"sys\".\"activity_asset\".\"effective\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "activity_asset_activity_id_kind_version_pk": { + "name": "activity_asset_activity_id_kind_version_pk", + "columns": [ + "activity_id", + "kind", + "version" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.activity_package": { + "name": "activity_package", + "schema": "sys", + "columns": { + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "paradigm": { + "name": "paradigm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "artifact": { + "name": "artifact", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_package_category_idx": { + "name": "activity_package_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "paradigm", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "activity_package_activity_id_version_pk": { + "name": "activity_package_activity_id_version_pk", + "columns": [ + "activity_id", + "version" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.asset_increment": { + "name": "asset_increment", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "diff": { + "name": "diff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence_entry_ids": { + "name": "evidence_entry_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'candidate'" + }, + "replay_report": { + "name": "replay_report", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "released_by": { + "name": "released_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "asset_increment_activity_idx": { + "name": "asset_increment_activity_idx", + "columns": [ + { + "expression": "activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.channel_instance": { + "name": "channel_instance", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "extract": { + "name": "extract", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "mapping": { + "name": "mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "fallback_position": { + "name": "fallback_position", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_window_ms": { + "name": "address_window_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 604800000 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.outbound_mail": { + "name": "outbound_mail", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_addr": { + "name": "from_addr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_addrs": { + "name": "to_addrs", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cc_addrs": { + "name": "cc_addrs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attachments": { + "name": "attachments", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.event_consumption": { + "name": "event_consumption", + "schema": "sys", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer": { + "name": "consumer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "consumption_key": { + "name": "consumption_key", + "columns": [ + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "event_consumption_event_id_consumer_pk": { + "name": "event_consumption_event_id_consumer_pk", + "columns": [ + "event_id", + "consumer" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.event_delivery": { + "name": "event_delivery", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "delivery_pending_idx": { + "name": "delivery_pending_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "delivery_target_idx": { + "name": "delivery_target_idx", + "columns": [ + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "event_delivery_event_id_event_id_fk": { + "name": "event_delivery_event_id_event_id_fk", + "tableFrom": "event_delivery", + "tableTo": "event", + "schemaTo": "sys", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.event": { + "name": "event", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation": { + "name": "correlation", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "trace": { + "name": "trace", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "event_target_idx": { + "name": "event_target_idx", + "columns": [ + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "event_occurred_idx": { + "name": "event_occurred_idx", + "columns": [ + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.listener": { + "name": "listener", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_filter": { + "name": "key_filter", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "condition": { + "name": "condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "semantic": { + "name": "semantic", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deadline": { + "name": "deadline", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "escalation": { + "name": "escalation", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'armed'" + } + }, + "indexes": { + "listener_armed_idx": { + "name": "listener_armed_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "listener_task_idx": { + "name": "listener_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.outbound_ref": { + "name": "outbound_ref", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "outbound_thread_idx": { + "name": "outbound_thread_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbound_addr_idx": { + "name": "outbound_addr_idx", + "columns": [ + { + "expression": "address", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.flow_state": { + "name": "flow_state", + "schema": "sys", + "columns": { + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "model": { + "name": "model", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.chat_card": { + "name": "chat_card", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient_id": { + "name": "recipient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "superseded_by": { + "name": "superseded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_reason": { + "name": "rejected_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chat_card_task_idx": { + "name": "chat_card_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_card_recipient_idx": { + "name": "chat_card_recipient_idx", + "columns": [ + { + "expression": "recipient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.work_order": { + "name": "work_order", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient_id": { + "name": "recipient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "done": { + "name": "done", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "work_order_task_idx": { + "name": "work_order_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "work_order_recipient_idx": { + "name": "work_order_recipient_idx", + "columns": [ + { + "expression": "recipient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.chat_message": { + "name": "chat_message", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_message_task_idx": { + "name": "chat_message_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.task_view": { + "name": "task_view", + "schema": "sys", + "columns": { + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "blocks": { + "name": "blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "html": { + "name": "html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'activating'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.llm_instance": { + "name": "llm_instance", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_key_cipher": { + "name": "api_key_cipher", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_use": { + "name": "system_use", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "llm_instance_name_key": { + "name": "llm_instance_name_key", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.replay_model": { + "name": "replay_model", + "schema": "sys", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "call": { + "name": "call", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "replay_model_recorded_idx": { + "name": "replay_model_recorded_idx", + "columns": [ + { + "expression": "recorded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.loop_state": { + "name": "loop_state", + "schema": "sys", + "columns": { + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "model": { + "name": "model", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "parsed": { + "name": "parsed", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "runtime": { + "name": "runtime", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.activity_memory": { + "name": "activity_memory", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "layer": { + "name": "layer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "statement": { + "name": "statement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "increment": { + "name": "increment", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pruned_at": { + "name": "pruned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "activity_memory_layer_idx": { + "name": "activity_memory_layer_idx", + "columns": [ + { + "expression": "activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "layer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.task_memory_seal": { + "name": "task_memory_seal", + "schema": "sys", + "columns": { + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "sealed_at": { + "name": "sealed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.task_memory": { + "name": "task_memory", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "side": { + "name": "side", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "stamp": { + "name": "stamp", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "governance": { + "name": "governance", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "links_to": { + "name": "links_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_ref": { + "name": "event_ref", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "task_memory_seq_key": { + "name": "task_memory_seq_key", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_memory_task_idx": { + "name": "task_memory_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.system_message": { + "name": "system_message", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "staff_id": { + "name": "staff_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_id": { + "name": "position_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "read_at": { + "name": "read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "card": { + "name": "card", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "answer": { + "name": "answer", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "event_ref": { + "name": "event_ref", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "message_staff_idx": { + "name": "message_staff_idx", + "columns": [ + { + "expression": "staff_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "message_unread_idx": { + "name": "message_unread_idx", + "columns": [ + { + "expression": "staff_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.position_grant": { + "name": "position_grant", + "schema": "sys", + "columns": { + "position_id": { + "name": "position_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object": { + "name": "object", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": {}, + "foreignKeys": { + "position_grant_position_id_position_id_fk": { + "name": "position_grant_position_id_position_id_fk", + "tableFrom": "position_grant", + "tableTo": "position", + "schemaTo": "sys", + "columnsFrom": [ + "position_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "position_grant_position_id_object_key_action_pk": { + "name": "position_grant_position_id_object_key_action_pk", + "columns": [ + "position_id", + "object_key", + "action" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.position": { + "name": "position", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "position_code_key": { + "name": "position_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.staff": { + "name": "staff", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account": { + "name": "account", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_id": { + "name": "position_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "staff_account_key": { + "name": "staff_account_key", + "columns": [ + { + "expression": "account", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "staff_position_idx": { + "name": "staff_position_idx", + "columns": [ + { + "expression": "position_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "staff_position_id_position_id_fk": { + "name": "staff_position_id_position_id_fk", + "tableFrom": "staff", + "tableTo": "position", + "schemaTo": "sys", + "columnsFrom": [ + "position_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.node_instance": { + "name": "node_instance", + "schema": "sys", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance": { + "name": "instance", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "running": { + "name": "running", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "waiting": { + "name": "waiting", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "activated": { + "name": "activated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "terminal": { + "name": "terminal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_digested": { + "name": "failure_digested", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "node_instance_task_idx": { + "name": "node_instance_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "node_instance_workflow_id_node_id_instance_pk": { + "name": "node_instance_workflow_id_node_id_instance_pk", + "columns": [ + "workflow_id", + "node_id", + "instance" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.process_package": { + "name": "process_package", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "process_package_state_idx": { + "name": "process_package_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "process_package_id_version_pk": { + "name": "process_package_id_version_pk", + "columns": [ + "id", + "version" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.workflow": { + "name": "workflow", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "package_id": { + "name": "package_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_version": { + "name": "package_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "source": { + "name": "source", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "initiation": { + "name": "initiation", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "ports": { + "name": "ports", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "vars": { + "name": "vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_state_idx": { + "name": "workflow_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.action_intent": { + "name": "action_intent", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'drafted'" + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "action_intent_task_idx": { + "name": "action_intent_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.task": { + "name": "task", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "paradigm": { + "name": "paradigm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runtime": { + "name": "runtime", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "terminal": { + "name": "terminal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "envelope": { + "name": "envelope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "caps": { + "name": "caps", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "used_steps": { + "name": "used_steps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "used_tokens": { + "name": "used_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "used_active_ms": { + "name": "used_active_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "used_human_calls": { + "name": "used_human_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_progress_at": { + "name": "last_progress_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "task_owner_idx": { + "name": "task_owner_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "runtime", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_workflow_idx": { + "name": "task_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_terminal_idx": { + "name": "task_terminal_idx", + "columns": [ + { + "expression": "terminal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.replay_tool": { + "name": "replay_tool", + "schema": "sys", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "call": { + "name": "call", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.tool_allowlist": { + "name": "tool_allowlist", + "schema": "sys", + "columns": { + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tool_allowlist_activity_id_tool_pk": { + "name": "tool_allowlist_activity_id_tool_pk", + "columns": [ + "activity_id", + "tool" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.tool": { + "name": "tool", + "schema": "sys", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "errors": { + "name": "errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "idempotent": { + "name": "idempotent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "timeout_ms": { + "name": "timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "connection_keys": { + "name": "connection_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pure": { + "name": "pure", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "owner_activity_id": { + "name": "owner_activity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_cipher": { + "name": "connection_cipher", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_effect_idx": { + "name": "tool_effect_idx", + "columns": [ + { + "expression": "effect", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_owner_idx": { + "name": "tool_owner_idx", + "columns": [ + { + "expression": "owner_activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/db/migrations/meta/_journal.json b/db/migrations/meta/_journal.json index 3ac7973..f28aaa8 100644 --- a/db/migrations/meta/_journal.json +++ b/db/migrations/meta/_journal.json @@ -43,6 +43,13 @@ "when": 1785088279529, "tag": "0005_broad_sprite", "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1785088864829, + "tag": "0006_easy_enchantress", + "breakpoints": true } ] } \ No newline at end of file diff --git a/docs/debt.md b/docs/debt.md index 4d8d3e9..b2d418c 100644 --- a/docs/debt.md +++ b/docs/debt.md @@ -11,4 +11,5 @@ | M1-2 | 条件求值 `passthroughEvaluator` 恒真 | `@sfeel` 属 M3;M1 的监听只用类型 + 关联键过滤,不用内容条件 | M3 | 进行中 | | M1-3 | ~~投递记录写入即止,尚无派发 worker~~ | 派发器已于 M2 落地(advisory lock 串行 + 消费幂等);消费者(任务 / 触发器)随 M4 / M5 注册 | M2 | 已清理 | | M2-1 | 派发器的消费者表为空 —— 投递只标记已送达 | 任务消费者属 task(M4),触发器消费者属 trigger(M5);投递链路本身已可测 | M4 / M5 | 进行中 | +| M5-1 | `process` 未用 Mastra workflow 编译器,流转由自持确定性状态机 + `sys.node_instance` 承担 | 编码规范 §6.1 规定流程包编译为 Mastra 图;二者语义等价(挂起/恢复由节点实例表承担),先落可测的自持实现,Mastra 编译器作为可替换 adapter 后补 —— usecase 不动 | M5 收尾 | 进行中 | | M2-2 | pg-boss 已装未用 | 定时作业(监听时限扫描、周期触发器)随 M5 落地;当下派发器用 setInterval | M5 | 进行中 |