diff --git a/api-report/apps/api/src/modules/trigger/contract.d.ts b/api-report/apps/api/src/modules/trigger/contract.d.ts new file mode 100644 index 0000000..c82ecea --- /dev/null +++ b/api-report/apps/api/src/modules/trigger/contract.d.ts @@ -0,0 +1,34 @@ +/** + * module: trigger —— 对外类型(编码规范 §10.1;语义出处 OS 3.1)。 + * + * 触发器三类,**独立配置存放**,判定与调用接口调起。 + * 触发器与流程包平级:增删启停不改流程包的任何版本。 + */ +import type { TriggerConfig, TriggerKind } from '@contracts'; +import type { AssemblyResult, EventScope } from './internal/domain/matching.ts'; +export type { AssemblyResult, EventScope, TriggerConfig, TriggerKind }; +export declare class TriggerInputMissingError extends Error { + readonly triggerId: string; + readonly fields: readonly string[]; + constructor(triggerId: string, fields: readonly string[]); +} +export type FireOutcome = { + readonly triggerId: string; + readonly workflowId?: string; + readonly skipped?: 'duplicate' | 'disabled' | 'input-missing'; + readonly missing?: readonly string[]; +}; +export type TriggerService = { + list(): Promise; + upsert(config: TriggerConfig): Promise; + setEnabled(id: string, enabled: boolean): Promise; + remove(id: string): Promise; + /** 事件到达:命中判定 —— 供 `event` 的 `TriggerMatcher` 端口使用 */ + match(scope: EventScope): Promise; + /** 命中即起单;同一事件对同一触发器**至多起一单**(事件 ID 为幂等键) */ + fireByEvent(triggerId: string, eventId: string, scope: EventScope): Promise; + /** 登记触发:人工起单的统一形态 —— 登记人即来源 */ + fireByRegistration(triggerId: string, by: string, data: Record): Promise; + /** 周期触发的到点扫描(由 pg-boss 定时调起) */ + sweepPeriodic(now: Date): Promise; +}; diff --git a/api-report/apps/api/src/modules/trigger/index.d.ts b/api-report/apps/api/src/modules/trigger/index.d.ts new file mode 100644 index 0000000..8af83e1 --- /dev/null +++ b/api-report/apps/api/src/modules/trigger/index.d.ts @@ -0,0 +1,11 @@ +/** + * module: trigger —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。 + */ +export type { AssemblyResult, EventScope, FireOutcome, TriggerConfig, TriggerKind, TriggerService } from './contract.ts'; +export { TriggerInputMissingError } from './contract.ts'; +export type { Clock, DeadLetterSink, IdGenerator, LedgerScanner, ProcessGateway, Sfeel, TriggerStore } from './port.ts'; +export type { TriggerServiceDeps } from './internal/usecase/trigger-service.ts'; +export { createTriggerService } from './internal/usecase/trigger-service.ts'; +export { applyOffset, assembleInput, dataCalendarKey, matchesEvent, nextCalendarFire, scopeOf, triggerIdempotencyKey, } from './internal/domain/matching.ts'; +export { createTriggerStore } from './internal/adapter/trigger-store.ts'; +export { triggerClaimTable, triggerTable } from './internal/adapter/trigger-schema.ts'; diff --git a/api-report/apps/api/src/modules/trigger/port.d.ts b/api-report/apps/api/src/modules/trigger/port.d.ts new file mode 100644 index 0000000..1a24a92 --- /dev/null +++ b/api-report/apps/api/src/modules/trigger/port.d.ts @@ -0,0 +1,63 @@ +/** + * module: trigger —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。 + */ +import type { TriggerConfig } from '@contracts'; +export type Clock = { + now(): Date; +}; +export type IdGenerator = { + next(): string; +}; +export type TriggerStore = { + list(): Promise; + get(id: string): Promise; + upsert(config: TriggerConfig): Promise; + setEnabled(id: string, enabled: boolean): Promise; + remove(id: string): Promise; + /** 幂等:同一键至多起一单 */ + claim(key: string): Promise; + lastFiredAt(id: string): Promise; + markFired(id: string, at: Date): Promise; +}; +/** 起单 —— app 注入 `process`(L7 同层?否:trigger 在 process 之上同层,经 app 装配) */ +export type ProcessGateway = { + start(input: { + packageId: string; + subject: string; + source: { + kind: 'periodic' | 'event' | 'registration'; + ref: string; + by?: string; + }; + data: Record; + }): Promise<{ + id: string; + }>; + /** 调用接口的必填字段 —— 装配缺项即起单失败 */ + requiredInputs(packageId: string): Promise; +}; +export type Sfeel = { + condition(expression: string, scope: Readonly>): boolean; + expression(expression: string, scope: Readonly>): unknown; +}; +/** 死信 —— app 注入 `message`(L2):起单失败入死信 */ +export type DeadLetterSink = { + report(input: { + triggerId: string; + reason: string; + detail: Record; + }): Promise; +}; +/** 数据日历型要读台账 —— app 注入 `ledger`(L1) */ +export type LedgerScanner = { + scan(input: { + domain: string; + object: string; + dateField: string; + filter?: string; + }): Promise; + date: string; + row: Record; + }>>; +}; diff --git a/apps/api/src/modules/trigger/CONTRACT.md b/apps/api/src/modules/trigger/CONTRACT.md new file mode 100644 index 0000000..8788ac0 --- /dev/null +++ b/apps/api/src/modules/trigger/CONTRACT.md @@ -0,0 +1,28 @@ +# module: trigger + +职责: 触发器三类(周期两型 / 事件 / 登记)的配置、判定与经调用接口起单; + 起单幂等;数据日历型的逐行扫描 +不负责: 流程包的结构与流转(见 process)、事件的投递(见 event —— 触发器是它的一个消费方) + +公开出口: index.ts + - 类型:`TriggerService` `TriggerConfig` `EventScope` `FireOutcome` `AssemblyResult` + - 错误:`TriggerInputMissingError` + - 端口类型:`TriggerStore` `ProcessGateway` `Sfeel` `DeadLetterSink` `LedgerScanner` + - 用例工厂:`createTriggerService(deps): TriggerService` + - 纯函数:`matchesEvent` `assembleInput` `triggerIdempotencyKey` `nextCalendarFire` + `dataCalendarKey` `applyOffset` `scopeOf` + - adapter:`createTriggerStore`;Drizzle 表:`triggerTable` `triggerClaimTable` + +层级: L7(编码规范 §4) +依赖: @contracts;@shared/db;process(L7 同层 → 经 `ProcessGateway` 端口由 app 注入); + port:`Sfeel` / `DeadLetterSink`(message)/ `LedgerScanner`(ledger) +被依赖: event(经 `TriggerMatcher` 端口,由 app 注入 —— event 是 L1,不反向依赖本模块)、app + +不变量(OS 3.1 / 6.3): + - **触发器与流程包平级**:配置独立存放,增删启停**不改流程包的任何版本** + - **只负责起单**:经调用接口调起流程包;流程包与活动内部不含任何触发逻辑 + - **同一事件流,不另设通路**:`match` 供 event 的三级消费判定使用 + - **同一事件对同一触发器至多起一单**:以事件 ID 为幂等键 + - **缺必填即起单失败入死信**,不静默丢弃 + - **数据日历型同一行同一日期只触发一次**(键含日期值);字段值变更后可再触发 + - 命中规则求值失败按**不命中**处置 —— 规则写坏了不让事件流停摆 diff --git a/apps/api/src/modules/trigger/__tests__/matching.test.ts b/apps/api/src/modules/trigger/__tests__/matching.test.ts new file mode 100644 index 0000000..aea0f42 --- /dev/null +++ b/apps/api/src/modules/trigger/__tests__/matching.test.ts @@ -0,0 +1,121 @@ +/** 触发器判定与输入装配 —— domain 测试零 mock(OS 3.1 / 6.3) */ +import { describe, expect, it } from 'vitest'; +import type { TriggerConfig } from '@contracts'; +import { evaluateCondition, evaluate as evalSfeel } from '@sfeel'; +import { + applyOffset, + assembleInput, + dataCalendarKey, + matchesEvent, + nextCalendarFire, + scopeOf, + triggerIdempotencyKey, +} from '../internal/domain/matching.ts'; + +const eventTrigger = (over: Partial = {}): TriggerConfig => + ({ + id: 'tr-rfq' as never, + name: '客户RFQ询价', + enabled: true, + processPackageId: 'quote' as never, + mapping: [ + { target: 'customerCode', source: { from: 'expression', sfeel: 'keys.customer' } }, + { target: 'channel', source: { from: 'constant', value: 'mail' } }, + ], + spec: { kind: 'event', rule: 'contains(subject, "询价")', eventType: 'data' }, + ...over, + }) as TriggerConfig; + +const scope = { + type: 'data', + keys: { customer: 'C001' }, + payload: { subject: '询价 RFQ - 车规 Analog' }, + source: 'channel', + occurredAt: '2026-07-27T02:00:00.000Z', +}; + +describe('matchesEvent —— 流程层的系统级事件监听', () => { + it('规则命中即起单候选', () => { + expect(matchesEvent(eventTrigger(), scope, evaluateCondition)).toBe(true); + }); + + it('规则不命中即跳过', () => { + expect(matchesEvent(eventTrigger(), { ...scope, payload: { subject: '普通问候' } }, evaluateCondition)).toBe(false); + }); + + it('停用的触发器恒不命中', () => { + expect(matchesEvent(eventTrigger({ enabled: false }), scope, evaluateCondition)).toBe(false); + }); + + it('事件类型不符即跳过', () => { + expect(matchesEvent(eventTrigger(), { ...scope, type: 'time' }, evaluateCondition)).toBe(false); + }); + + it('规则写坏了按不命中处置 —— 不让事件流停摆', () => { + const broken = eventTrigger({ spec: { kind: 'event', rule: 'contains(', eventType: 'data' } } as never); + expect(matchesEvent(broken, scope, evaluateCondition)).toBe(false); + }); +}); + +describe('assembleInput —— 缺必填即起单失败入死信', () => { + const evalExpr = (expr: string, s: Readonly>): unknown => evalSfeel(expr, s); + + it('必填齐备即 ready', () => { + const result = assembleInput(eventTrigger(), scopeOf(scope), ['customerCode'], evalExpr); + expect(result).toEqual({ kind: 'ready', data: { customerCode: 'C001', channel: 'mail' } }); + }); + + it('缺必填即 missing,并指出缺谁', () => { + const result = assembleInput(eventTrigger(), scopeOf({ ...scope, keys: {} }), ['customerCode'], evalExpr); + expect(result).toEqual({ kind: 'missing', fields: ['customerCode'] }); + }); + + it('常量映射不依赖事件', () => { + const result = assembleInput(eventTrigger(), {}, ['channel'], evalExpr); + expect(result.kind).toBe('ready'); + }); +}); + +describe('幂等键', () => { + it('同一事件对同一触发器至多起一单', () => { + expect(triggerIdempotencyKey('t1', 'e1')).toBe('trigger:t1:e1'); + expect(triggerIdempotencyKey('t1', 'e1')).not.toBe(triggerIdempotencyKey('t1', 'e2')); + }); + + it('数据日历型:同一行同一日期只触发一次', () => { + expect(dataCalendarKey('t1', 'pn=LM358', '2026-08-01')).toBe('trigger:t1:pn=LM358:2026-08-01'); + // 日期变了就能再触发 + expect(dataCalendarKey('t1', 'pn=LM358', '2026-09-01')).not.toBe(dataCalendarKey('t1', 'pn=LM358', '2026-08-01')); + }); +}); + +describe('周期触发', () => { + it('日历型:每天到点', () => { + const after = new Date('2026-07-27T01:00:00.000Z'); + const next = nextCalendarFire('daily 09:00', after); + expect(next).toBeInstanceOf(Date); + expect(next!.getHours()).toBe(9); + expect(next!.getTime()).toBeGreaterThan(after.getTime()); + }); + + it('已过点即顺延到次日', () => { + const morning = new Date('2026-07-27T00:00:00.000Z'); + morning.setHours(10, 0, 0, 0); + const next = nextCalendarFire('daily 09:00', morning); + expect(next!.getDate()).toBe(morning.getDate() + 1); + }); + + it('周期表达式不认即 undefined', () => { + expect(nextCalendarFire('每天九点', new Date())).toBeUndefined(); + }); + + it('数据日历型偏移:合同到期前 N 天', () => { + const due = new Date('2026-08-10T00:00:00.000Z'); + expect(applyOffset(due, '-P3D').toISOString().slice(0, 10)).toBe('2026-08-07'); + expect(applyOffset(due, 'P0D').toISOString().slice(0, 10)).toBe('2026-08-10'); + }); + + it('非法偏移即报错', () => { + expect(() => applyOffset(new Date(), '前三天')).toThrow(/非法日历偏移/); + }); +}); diff --git a/apps/api/src/modules/trigger/contract.ts b/apps/api/src/modules/trigger/contract.ts new file mode 100644 index 0000000..39070cb --- /dev/null +++ b/apps/api/src/modules/trigger/contract.ts @@ -0,0 +1,43 @@ +/** + * module: trigger —— 对外类型(编码规范 §10.1;语义出处 OS 3.1)。 + * + * 触发器三类,**独立配置存放**,判定与调用接口调起。 + * 触发器与流程包平级:增删启停不改流程包的任何版本。 + */ +import type { TriggerConfig, TriggerKind } from '@contracts'; +import type { AssemblyResult, EventScope } from './internal/domain/matching.ts'; + +export type { AssemblyResult, EventScope, TriggerConfig, TriggerKind }; + +export class TriggerInputMissingError extends Error { + constructor( + readonly triggerId: string, + readonly fields: readonly string[], + ) { + super(`触发器 ${triggerId} 起单失败:缺必填字段 ${fields.join('、')} —— 入死信`); + this.name = 'TriggerInputMissingError'; + } +} + +export type FireOutcome = { + readonly triggerId: string; + readonly workflowId?: string; + readonly skipped?: 'duplicate' | 'disabled' | 'input-missing'; + readonly missing?: readonly string[]; +}; + +export type TriggerService = { + list(): Promise; + upsert(config: TriggerConfig): Promise; + setEnabled(id: string, enabled: boolean): Promise; + remove(id: string): Promise; + + /** 事件到达:命中判定 —— 供 `event` 的 `TriggerMatcher` 端口使用 */ + match(scope: EventScope): Promise; + /** 命中即起单;同一事件对同一触发器**至多起一单**(事件 ID 为幂等键) */ + fireByEvent(triggerId: string, eventId: string, scope: EventScope): Promise; + /** 登记触发:人工起单的统一形态 —— 登记人即来源 */ + fireByRegistration(triggerId: string, by: string, data: Record): Promise; + /** 周期触发的到点扫描(由 pg-boss 定时调起) */ + sweepPeriodic(now: Date): Promise; +}; diff --git a/apps/api/src/modules/trigger/index.ts b/apps/api/src/modules/trigger/index.ts new file mode 100644 index 0000000..4a3f1ca --- /dev/null +++ b/apps/api/src/modules/trigger/index.ts @@ -0,0 +1,23 @@ +/** + * module: trigger —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。 + */ +export type { AssemblyResult, EventScope, FireOutcome, TriggerConfig, TriggerKind, TriggerService } from './contract.ts'; +export { TriggerInputMissingError } from './contract.ts'; + +export type { Clock, DeadLetterSink, IdGenerator, LedgerScanner, ProcessGateway, Sfeel, TriggerStore } from './port.ts'; + +export type { TriggerServiceDeps } from './internal/usecase/trigger-service.ts'; +export { createTriggerService } from './internal/usecase/trigger-service.ts'; + +export { + applyOffset, + assembleInput, + dataCalendarKey, + matchesEvent, + nextCalendarFire, + scopeOf, + triggerIdempotencyKey, +} from './internal/domain/matching.ts'; + +export { createTriggerStore } from './internal/adapter/trigger-store.ts'; +export { triggerClaimTable, triggerTable } from './internal/adapter/trigger-schema.ts'; diff --git a/apps/api/src/modules/trigger/internal/adapter/trigger-schema.ts b/apps/api/src/modules/trigger/internal/adapter/trigger-schema.ts new file mode 100644 index 0000000..e6769ce --- /dev/null +++ b/apps/api/src/modules/trigger/internal/adapter/trigger-schema.ts @@ -0,0 +1,27 @@ +/** + * trigger 的 sys schema 表(编码规范 §7 / ADR-002)。 + * 触发器配置**独立存放**于系统自持存储 —— 不入流程包结构、不随流程包版本(OS 3.1)。 + */ +import { boolean, index, jsonb, text, timestamp } from 'drizzle-orm/pg-core'; +import { sys } from '@shared/db.ts'; + +export const triggerTable = sys.table( + 'trigger', + { + id: text('id').primaryKey(), + name: text('name').notNull(), + enabled: boolean('enabled').notNull().default(true), + processPackageId: text('process_package_id').notNull(), + mapping: jsonb('mapping').notNull().default([]), + spec: jsonb('spec').notNull(), + lastFiredAt: timestamp('last_fired_at', { withTimezone: true }), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + }, + (t) => [index('trigger_enabled_idx').on(t.enabled)], +); + +/** 起单幂等表:同一事件对同一触发器至多起一单 */ +export const triggerClaimTable = sys.table('trigger_claim', { + key: text('key').primaryKey(), + claimedAt: timestamp('claimed_at', { withTimezone: true }).notNull().defaultNow(), +}); diff --git a/apps/api/src/modules/trigger/internal/adapter/trigger-store.ts b/apps/api/src/modules/trigger/internal/adapter/trigger-store.ts new file mode 100644 index 0000000..bda4292 --- /dev/null +++ b/apps/api/src/modules/trigger/internal/adapter/trigger-store.ts @@ -0,0 +1,73 @@ +/** + * trigger 存储 adapter —— Drizzle 只准出现在 adapter(编码规范 §5.3)。 + */ +import { eq } from 'drizzle-orm'; +import type { TriggerConfig } from '@contracts'; +import { triggerConfigSchema } from '@contracts'; +import type { Db } from '@shared/db.ts'; +import type { TriggerStore } from '../../port.ts'; +import { triggerClaimTable, triggerTable } from './trigger-schema.ts'; + +type Row = typeof triggerTable.$inferSelect; + +function toConfig(row: Row): TriggerConfig { + return triggerConfigSchema.parse({ + id: row.id, + name: row.name, + enabled: row.enabled, + processPackageId: row.processPackageId, + mapping: row.mapping, + spec: row.spec, + }); +} + +export function createTriggerStore(db: Db): TriggerStore { + return { + async list() { + const rows = await db.select().from(triggerTable).orderBy(triggerTable.name); + return rows.map(toConfig); + }, + async get(id) { + const rows = await db.select().from(triggerTable).where(eq(triggerTable.id, id)).limit(1); + return rows[0] === undefined ? undefined : toConfig(rows[0]); + }, + async upsert(config) { + const values = { + id: config.id, + name: config.name, + enabled: config.enabled, + processPackageId: config.processPackageId, + mapping: config.mapping, + spec: config.spec, + }; + const [row] = await db + .insert(triggerTable) + .values(values) + .onConflictDoUpdate({ target: triggerTable.id, set: values }) + .returning(); + if (row === undefined) throw new Error('触发器写入未返回行'); + return toConfig(row); + }, + async setEnabled(id, enabled) { + await db.update(triggerTable).set({ enabled }).where(eq(triggerTable.id, id)); + }, + async remove(id) { + await db.delete(triggerTable).where(eq(triggerTable.id, id)); + }, + async claim(key) { + const inserted = await db + .insert(triggerClaimTable) + .values({ key }) + .onConflictDoNothing() + .returning({ key: triggerClaimTable.key }); + return inserted.length > 0; + }, + async lastFiredAt(id) { + const rows = await db.select({ at: triggerTable.lastFiredAt }).from(triggerTable).where(eq(triggerTable.id, id)).limit(1); + return rows[0]?.at ?? undefined; + }, + async markFired(id, at) { + await db.update(triggerTable).set({ lastFiredAt: at }).where(eq(triggerTable.id, id)); + }, + }; +} diff --git a/apps/api/src/modules/trigger/internal/domain/matching.ts b/apps/api/src/modules/trigger/internal/domain/matching.ts new file mode 100644 index 0000000..090ad2a --- /dev/null +++ b/apps/api/src/modules/trigger/internal/domain/matching.ts @@ -0,0 +1,137 @@ +/** + * 触发器判定 —— 纯逻辑,零 IO(编码规范 §5.3;语义出处 OS 3.1)。 + * + * 「触发器 = **流程层的系统级事件监听**:消费同一条事件流,动作是**起单**。 + * 三类:周期(日历型 / 数据日历型)、事件、登记。 + * 触发器与工作流**平级**:不入流程包结构、不随流程包版本; + * 只负责起单,经调用接口调起流程包 —— 流程包与活动内部不含任何触发逻辑。」 + */ +import type { TriggerConfig } from '@contracts'; + +export type ConditionEvaluator = (expression: string, scope: Readonly>) => boolean; +export type ExpressionEvaluator = (expression: string, scope: Readonly>) => unknown; + +/** 事件触发的求值输入:事件信封与载荷字段 */ +export type EventScope = { + readonly type: string; + readonly keys: Readonly>; + readonly payload: Readonly>; + readonly source: string; + readonly occurredAt: string; +}; + +export function scopeOf(scope: EventScope): Record { + return { type: scope.type, keys: scope.keys, source: scope.source, occurredAt: scope.occurredAt, ...scope.payload }; +} + +/** 事件触发器是否命中 —— 命中规则用 S-FEEL,静态可检 */ +export function matchesEvent(config: TriggerConfig, scope: EventScope, evaluate: ConditionEvaluator): boolean { + if (!config.enabled) return false; + if (config.spec.kind !== 'event') return false; + if (config.spec.eventType !== scope.type) return false; + try { + return evaluate(config.spec.rule, scopeOf(scope)); + } catch { + // 规则写坏了不该让事件流停摆:按不命中处置 + return false; + } +} + +/* ── 触发器输入装配(OS 3.1) ────────────────────────────────────────── */ + +export type AssemblyResult = + | { readonly kind: 'ready'; readonly data: Record } + /** 缺必填字段则**起单失败、入死信** */ + | { readonly kind: 'missing'; readonly fields: readonly string[] }; + +export function assembleInput( + config: TriggerConfig, + scope: Readonly>, + required: readonly string[], + evaluateExpression: ExpressionEvaluator, +): AssemblyResult { + const data: Record = {}; + for (const mapping of config.mapping) { + switch (mapping.source.from) { + case 'constant': + data[mapping.target] = mapping.source.value; + break; + case 'expression': + data[mapping.target] = evaluateExpression(mapping.source.sfeel, scope); + break; + case 'initiation': + case 'output-port': + case 'workflow-var': + data[mapping.target] = pick(scope, mapping.source.from === 'workflow-var' ? mapping.source.name : mapping.source.path); + break; + } + } + const missing = required.filter((field) => data[field] === undefined || data[field] === null); + return missing.length > 0 ? { kind: 'missing', fields: missing } : { kind: 'ready', data }; +} + +function pick(scope: Readonly>, path: string): unknown { + return path.split('.').reduce((acc, seg) => { + if (acc === null || acc === undefined || typeof acc !== 'object') return undefined; + return (acc as Record)[seg]; + }, scope); +} + +/* ── 幂等:同一事件对同一触发器**至多起一单**,以事件 ID 为幂等键(OS 6.3) ── */ + +export function triggerIdempotencyKey(triggerId: string, eventId: string): string { + return `trigger:${triggerId}:${eventId}`; +} + +/* ── 周期触发:日历型与数据日历型(OS 3.1) ─────────────────────────── */ + +/** 极简日历表达式:`daily HH:mm` / `weekly <1-7> HH:mm` / `monthly <1-28> HH:mm` */ +export function nextCalendarFire(expression: string, after: Date): Date | undefined { + const daily = /^daily\s+(\d{1,2}):(\d{2})$/.exec(expression); + if (daily !== null) return nextAt(after, Number(daily[1]), Number(daily[2])); + + const weekly = /^weekly\s+([1-7])\s+(\d{1,2}):(\d{2})$/.exec(expression); + if (weekly !== null) { + let candidate = nextAt(after, Number(weekly[2]), Number(weekly[3])); + const target = Number(weekly[1]) % 7; + for (let i = 0; i < 7 && candidate.getDay() !== target; i += 1) { + candidate = new Date(candidate.getTime() + 86_400_000); + } + return candidate; + } + + const monthly = /^monthly\s+(\d{1,2})\s+(\d{1,2}):(\d{2})$/.exec(expression); + if (monthly !== null) { + let candidate = nextAt(after, Number(monthly[2]), Number(monthly[3])); + const day = Number(monthly[1]); + for (let i = 0; i < 62 && candidate.getDate() !== day; i += 1) { + candidate = new Date(candidate.getTime() + 86_400_000); + } + return candidate; + } + return undefined; +} + +function nextAt(after: Date, hour: number, minute: number): Date { + const candidate = new Date(after); + candidate.setSeconds(0, 0); + candidate.setHours(hour, minute); + if (candidate.getTime() <= after.getTime()) candidate.setDate(candidate.getDate() + 1); + return candidate; +} + +/** + * 数据日历型:按台账日期字段 + 偏移 + 过滤条件**逐行起单**。 + * 「同一行同一日期只触发一次,字段值变更后可再触发。」 + */ +export function dataCalendarKey(triggerId: string, rowKey: string, dateValue: string): string { + return `trigger:${triggerId}:${rowKey}:${dateValue}`; +} + +/** 偏移:`-P3D` 表示日期字段前 3 天 */ +export function applyOffset(date: Date, offset: string): Date { + const m = /^(-?)P(?:(\d+)D)?$/.exec(offset); + if (m === null) throw new Error(`非法日历偏移:${offset}`); + const days = Number(m[2] ?? 0) * (m[1] === '-' ? -1 : 1); + return new Date(date.getTime() + days * 86_400_000); +} diff --git a/apps/api/src/modules/trigger/internal/usecase/trigger-service.ts b/apps/api/src/modules/trigger/internal/usecase/trigger-service.ts new file mode 100644 index 0000000..d4e547e --- /dev/null +++ b/apps/api/src/modules/trigger/internal/usecase/trigger-service.ts @@ -0,0 +1,132 @@ +/** + * trigger 用例 —— 判定与起单(编码规范 §5.3;语义出处 OS 3.1)。 + */ +import type { TriggerConfig } from '@contracts'; +import type { EventScope, FireOutcome, TriggerService } from '../../contract.ts'; +import type { Clock, DeadLetterSink, IdGenerator, LedgerScanner, ProcessGateway, Sfeel, TriggerStore } from '../../port.ts'; +import { + applyOffset, + assembleInput, + dataCalendarKey, + matchesEvent, + nextCalendarFire, + scopeOf, + triggerIdempotencyKey, +} from '../domain/matching.ts'; + +export type TriggerServiceDeps = { + readonly store: TriggerStore; + readonly process: ProcessGateway; + readonly sfeel: Sfeel; + readonly deadLetters: DeadLetterSink; + readonly ledger: LedgerScanner; + readonly clock: Clock; + readonly ids: IdGenerator; +}; + +export function createTriggerService(deps: TriggerServiceDeps): TriggerService { + /** 装配 → 起单;缺必填即入死信,不静默丢弃 */ + async function fire( + config: TriggerConfig, + source: { kind: 'periodic' | 'event' | 'registration'; ref: string; by?: string }, + scope: Record, + subject: string, + ): Promise { + const required = await deps.process.requiredInputs(config.processPackageId); + const assembled = assembleInput(config, scope, required, deps.sfeel.expression); + if (assembled.kind === 'missing') { + await deps.deadLetters.report({ + triggerId: config.id, + reason: `起单失败:缺必填字段 ${assembled.fields.join('、')}`, + detail: { scope }, + }); + return { triggerId: config.id, skipped: 'input-missing', missing: assembled.fields }; + } + const workflow = await deps.process.start({ + packageId: config.processPackageId, + subject, + source, + data: assembled.data, + }); + await deps.store.markFired(config.id, deps.clock.now()); + return { triggerId: config.id, workflowId: workflow.id }; + } + + return { + async list() { + return deps.store.list(); + }, + async upsert(config) { + return deps.store.upsert(config); + }, + async setEnabled(id, enabled) { + await deps.store.setEnabled(id, enabled); + }, + async remove(id) { + await deps.store.remove(id); + }, + + /** 供 event 的 TriggerMatcher 端口:同一事件流,不另设通路 */ + async match(scope: EventScope) { + const all = await deps.store.list(); + return all.filter((c) => matchesEvent(c, scope, deps.sfeel.condition)).map((c) => c.id); + }, + + async fireByEvent(triggerId, eventId, scope) { + const config = await deps.store.get(triggerId); + if (config === undefined || !config.enabled) return { triggerId, skipped: 'disabled' }; + // 同一事件对同一触发器至多起一单 + if (!(await deps.store.claim(triggerIdempotencyKey(triggerId, eventId)))) { + return { triggerId, skipped: 'duplicate' }; + } + return fire(config, { kind: 'event', ref: eventId }, scopeOf(scope), config.name); + }, + + /** 登记触发:登记人即来源 */ + async fireByRegistration(triggerId, by, data) { + const config = await deps.store.get(triggerId); + if (config === undefined || !config.enabled) return { triggerId, skipped: 'disabled' }; + return fire(config, { kind: 'registration', ref: `registration:${by}`, by }, data, config.name); + }, + + /** 周期扫描:日历型按表达式到点;数据日历型逐行起单 */ + async sweepPeriodic(now) { + const outcomes: FireOutcome[] = []; + for (const config of await deps.store.list()) { + if (!config.enabled || config.spec.kind !== 'periodic') continue; + + if (config.spec.mode === 'calendar') { + const last = (await deps.store.lastFiredAt(config.id)) ?? new Date(now.getTime() - 86_400_000); + const due = nextCalendarFire(config.spec.calendar, last); + if (due === undefined || due.getTime() > now.getTime()) continue; + if (!(await deps.store.claim(`${config.id}:${due.toISOString()}`))) continue; + outcomes.push(await fire(config, { kind: 'periodic', ref: due.toISOString() }, { firedAt: due.toISOString() }, config.name)); + continue; + } + + // 数据日历型:日期字段 + 偏移 + 过滤条件 + const spec = config.spec; + const rows = await deps.ledger.scan({ + domain: spec.domain, + object: spec.object, + dateField: spec.dateField, + ...(spec.filter === undefined ? {} : { filter: spec.filter }), + }); + for (const row of rows) { + const due = applyOffset(new Date(row.date), spec.offset); + if (due.getTime() > now.getTime()) continue; + const rowKey = Object.entries(row.key) + .sort(([a], [b]) => (a < b ? -1 : 1)) + .map(([k, v]) => `${k}=${String(v)}`) + .join('|'); + // 同一行同一日期只触发一次;字段值变更后可再触发 + if (!(await deps.store.claim(dataCalendarKey(config.id, rowKey, row.date)))) continue; + outcomes.push( + await fire(config, { kind: 'periodic', ref: `${rowKey}@${row.date}` }, { ...row.row, ...row.key }, config.name), + ); + } + } + return outcomes; + }, + }; +} diff --git a/apps/api/src/modules/trigger/package.json b/apps/api/src/modules/trigger/package.json new file mode 100644 index 0000000..c70a682 --- /dev/null +++ b/apps/api/src/modules/trigger/package.json @@ -0,0 +1 @@ +{ "name": "@modules/trigger", "version": "0.0.0", "private": true, "type": "module", "exports": { ".": "./index.ts" } } diff --git a/apps/api/src/modules/trigger/port.ts b/apps/api/src/modules/trigger/port.ts new file mode 100644 index 0000000..5fe5d37 --- /dev/null +++ b/apps/api/src/modules/trigger/port.ts @@ -0,0 +1,51 @@ +/** + * module: trigger —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。 + */ +import type { TriggerConfig } from '@contracts'; + +export type Clock = { now(): Date }; +export type IdGenerator = { next(): string }; + +export type TriggerStore = { + list(): Promise; + get(id: string): Promise; + upsert(config: TriggerConfig): Promise; + setEnabled(id: string, enabled: boolean): Promise; + remove(id: string): Promise; + /** 幂等:同一键至多起一单 */ + claim(key: string): Promise; + lastFiredAt(id: string): Promise; + markFired(id: string, at: Date): Promise; +}; + +/** 起单 —— app 注入 `process`(L7 同层?否:trigger 在 process 之上同层,经 app 装配) */ +export type ProcessGateway = { + start(input: { + packageId: string; + subject: string; + source: { kind: 'periodic' | 'event' | 'registration'; ref: string; by?: string }; + data: Record; + }): Promise<{ id: string }>; + /** 调用接口的必填字段 —— 装配缺项即起单失败 */ + requiredInputs(packageId: string): Promise; +}; + +export type Sfeel = { + condition(expression: string, scope: Readonly>): boolean; + expression(expression: string, scope: Readonly>): unknown; +}; + +/** 死信 —— app 注入 `message`(L2):起单失败入死信 */ +export type DeadLetterSink = { + report(input: { triggerId: string; reason: string; detail: Record }): Promise; +}; + +/** 数据日历型要读台账 —— app 注入 `ledger`(L1) */ +export type LedgerScanner = { + scan(input: { + domain: string; + object: string; + dateField: string; + filter?: string; + }): Promise; date: string; row: Record }>>; +}; diff --git a/db/migrations/0007_swift_silver_samurai.sql b/db/migrations/0007_swift_silver_samurai.sql new file mode 100644 index 0000000..79f1043 --- /dev/null +++ b/db/migrations/0007_swift_silver_samurai.sql @@ -0,0 +1,17 @@ +CREATE TABLE "sys"."trigger_claim" ( + "key" text PRIMARY KEY NOT NULL, + "claimed_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "sys"."trigger" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + "process_package_id" text NOT NULL, + "mapping" jsonb DEFAULT '[]'::jsonb NOT NULL, + "spec" jsonb NOT NULL, + "last_fired_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE INDEX "trigger_enabled_idx" ON "sys"."trigger" USING btree ("enabled"); \ No newline at end of file diff --git a/db/migrations/meta/0007_snapshot.json b/db/migrations/meta/0007_snapshot.json new file mode 100644 index 0000000..8fbd367 --- /dev/null +++ b/db/migrations/meta/0007_snapshot.json @@ -0,0 +1,3000 @@ +{ + "id": "0e9cf97f-0886-4ab7-ac9a-a11e5f23cdf9", + "prevId": "8ff12b4a-4df5-4281-9322-9118df22e5db", + "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 + }, + "sys.trigger_claim": { + "name": "trigger_claim", + "schema": "sys", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.trigger": { + "name": "trigger", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "process_package_id": { + "name": "process_package_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mapping": { + "name": "mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "spec": { + "name": "spec", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "trigger_enabled_idx": { + "name": "trigger_enabled_idx", + "columns": [ + { + "expression": "enabled", + "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 f28aaa8..305a9ef 100644 --- a/db/migrations/meta/_journal.json +++ b/db/migrations/meta/_journal.json @@ -50,6 +50,13 @@ "when": 1785088864829, "tag": "0006_easy_enchantress", "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1785089035016, + "tag": "0007_swift_silver_samurai", + "breakpoints": true } ] } \ No newline at end of file