/** * 统一事件标准(编码规范 §8.1;语义出处 OS 06)。 * * 系统向活动只以统一信封抛四类事件;一条事件流,三级消费(任务监听 / 触发器 / 消息)。 * 投递五诺(先持久后投递 / 同任务按序 / 至少一次 + 幂等 / 处理有回执 / 扇出与幂等) * 是 `event` 模块的实现承诺,不在本文件表达 —— 本文件只定信封的形状。 */ import { z } from 'zod'; import { channelIdSchema, deliveryIdSchema, eventIdSchema, listenerIdSchema, positionIdSchema, taskIdSchema, triggerIdSchema, workflowIdSchema, } from './id.ts'; import { controlSemanticSchema } from './terminal.ts'; /* ── 四类事件 ─────────────────────────────────────────────────────────── */ export const EVENT_TYPES = ['data', 'time', 'human', 'control'] as const; export const eventTypeSchema = z.enum(EVENT_TYPES); export type EventType = z.infer; export const EVENT_TYPE_LABEL: Readonly> = { data: '数据', time: '时间', human: '人', control: '控制', }; /* ── 关联:消费目标 + 起因 ────────────────────────────────────────────── */ /** 消费目标四类(OS 6.1):任务 / 触发器 / 工作流 / 消息收件岗位 */ export const eventTargetSchema = z.discriminatedUnion('kind', [ z.object({ kind: z.literal('task'), id: taskIdSchema }), z.object({ kind: z.literal('trigger'), id: triggerIdSchema }), z.object({ kind: z.literal('workflow'), id: workflowIdSchema }), z.object({ kind: z.literal('message-position'), id: positionIdSchema }), ]); export type EventTarget = z.infer; /** 起因:数据对象 / 卡 ID / 定时器 ID —— 「这条事件是被什么激起的」 */ export const eventCauseSchema = z.object({ kind: z.enum(['ledger-write', 'card-reply', 'timer', 'inbound', 'governance', 'engine']), ref: z.string().min(1), }); export type EventCause = z.infer; /* ── 来源 ─────────────────────────────────────────────────────────────── */ export const EVENT_SOURCES = ['channel', 'engine', 'desk', 'governance'] as const; export const eventSourceKindSchema = z.enum(EVENT_SOURCES); export type EventSourceKind = z.infer; export const eventSourceSchema = z.object({ kind: eventSourceKindSchema, /** 通道来源必带通道实例 ID */ channelId: channelIdSchema.optional(), detail: z.string().optional(), }); export type EventSource = z.infer; /* ── 载荷 ─────────────────────────────────────────────────────────────── */ /** 载荷:schema 化内容 + 原件指针(如邮件原文的存档地址) */ export const eventPayloadSchema = z.object({ data: z.record(z.string(), z.unknown()), /** 原件指针 —— 存档地址,不是内容本身 */ originRef: z.string().optional(), }); export type EventPayload = z.infer; /* ── 信封六字段 ───────────────────────────────────────────────────────── */ export const envelopeSchema = z.object({ /** ① 事件 ID —— 全局唯一,幂等键 */ id: eventIdSchema, /** ② 类型 —— 四类之一 */ type: eventTypeSchema, /** * ③ 关联 —— 消费目标 + 起因。 * * `target` 可空:存储态的事件未必知道自己该被谁消费(归属解析与三级消费判定在后)。 * **投递态**的单目标由 `Delivery.target` 承载 —— 「信封关联 = 单目标」的承诺(OS 6.3 扇出) * 在投递记录上成立,存储的原始事件只有一份。 */ correlation: z.object({ target: eventTargetSchema.optional(), cause: eventCauseSchema, /** 关联键:归属解析与监听过滤的匹配面(单号、线程 ID、域.对象 主键…) */ keys: z.record(z.string(), z.string()).default({}), }), /** ④ 载荷 */ payload: eventPayloadSchema, /** ⑤ 时间与来源 */ occurredAt: z.iso.datetime(), source: eventSourceSchema, /** ⑥ 留痕指针 —— 事件存储位置;投递入任务后另记任务记忆指针 */ trace: z.object({ storeRef: z.string().min(1), taskMemoryRef: z.string().optional(), }), }); export type Envelope = z.infer; /** * 尚未落存储的信封草稿 —— `publish` 的入参。 * * `target` 在此**可空**:原始事件未必知道自己该被谁消费。归属解析(6.2)与三级消费判定 * (任务监听 / 触发器 / 消息兜底,6.3)由 `event` 模块完成,并为每个命中目标生成一份 * 独立投递 —— 投递出去的信封「关联 = 单目标」,这正是扇出与幂等承诺的形态。 * `storeRef` 由 event adapter 落库时补。 */ export const envelopeDraftSchema = envelopeSchema .omit({ id: true, trace: true }) .extend({ id: eventIdSchema.optional() }); export type EnvelopeDraft = z.infer; /* ── 四类事件的载荷细化 ──────────────────────────────────────────────── */ /** 控制事件载荷:三语义 + 发起人 */ export const controlPayloadSchema = z.object({ semantic: controlSemanticSchema, reason: z.string().optional(), issuedBy: z.string().min(1), }); export type ControlPayload = z.infer; /** 时间事件载荷:哪个监听 / 定时器到点 */ export const timePayloadSchema = z.object({ listenerId: listenerIdSchema.optional(), timerRef: z.string().min(1), /** 时限阶梯:提醒 → 升级(OS 03 催办阶梯) */ escalation: z.enum(['nudge', 'reroute', 'escalate']).optional(), }); export type TimePayload = z.infer; /** 数据事件载荷:域.对象.主键 的正式版写入 —— 草稿写不抛事件(OS 09) */ export const dataPayloadSchema = z.object({ domain: z.string().min(1), object: z.string().min(1), primaryKey: z.record(z.string(), z.union([z.string(), z.number()])), changed: z.array(z.string()).default([]), operation: z.enum(['insert', 'update', 'delete']), }); export type DataPayload = z.infer; /* ── 监听声明(OS 6.3 / AM 4.4) ─────────────────────────────────────── */ /** * 处理语义:loop 事件即观察 / flow 事件即步的完成条件。 * 这是**引擎层**的区分,交互层不可见(编码规范 §9 双层边界)。 */ export const LISTENER_SEMANTICS = ['observe', 'complete-step'] as const; export const listenerSemanticSchema = z.enum(LISTENER_SEMANTICS); export type ListenerSemantic = z.infer; export const listenerSpecSchema = z.object({ /** 静态(随业务模型)/ 动态(运行中由 Agent 机制临场登记) */ origin: z.enum(['static', 'dynamic']), eventType: eventTypeSchema, /** 关联键过滤 —— 「凭什么认出就是它」 */ keyFilter: z.record(z.string(), z.string()).default({}), /** 内容条件:S-FEEL 表达式,对信封与载荷字段求值 */ condition: z.string().optional(), semantic: listenerSemanticSchema, /** * 时限必配 —— 无来源又无时限的监听是「幽灵等待」,安装静态审必须拒绝(AM 4.4)。 * ISO-8601 duration,如 P3D。 */ deadline: z.string().min(2), /** 到期动作阶梯:提醒 → 换道 → 升级 */ escalation: z.array(z.enum(['nudge', 'reroute', 'escalate'])).default(['nudge', 'escalate']), }); export type ListenerSpec = z.infer; export const registeredListenerSchema = listenerSpecSchema.extend({ id: listenerIdSchema, taskId: taskIdSchema, registeredAt: z.iso.datetime(), expiresAt: z.iso.datetime(), state: z.enum(['armed', 'fired', 'expired', 'cancelled']), }); export type RegisteredListener = z.infer; /* ── 投递记录 ─────────────────────────────────────────────────────────── */ export const deliverySchema = z.object({ id: deliveryIdSchema, eventId: eventIdSchema, target: eventTargetSchema, attempt: z.number().int().min(1), state: z.enum(['pending', 'delivered', 'failed', 'dead-letter']), lastError: z.string().optional(), }); export type Delivery = z.infer; /* ── 归属解析(OS 6.2) ──────────────────────────────────────────────── */ /** 匹配次序确定、逐级短路:① 线程精确 ② 关联键命中监听 ③ 地址 + 时间窗 */ export const OWNERSHIP_RULES = ['thread', 'listener-key', 'address-window'] as const; export const ownershipRuleSchema = z.enum(OWNERSHIP_RULES); export type OwnershipRule = z.infer; export const ownershipResolutionSchema = z.discriminatedUnion('outcome', [ z.object({ outcome: z.literal('owned'), rule: ownershipRuleSchema, taskId: taskIdSchema, }), z.object({ outcome: z.literal('unowned'), /** 宁交人,不错投 —— 歧义与提取失败一律降级为无主 */ reason: z.enum(['no-match', 'ambiguous', 'extract-failed']), /** 无主入站的相关人 = 通道实例配置的默认认领岗位 */ fallbackPosition: positionIdSchema.optional(), }), ]); export type OwnershipResolution = z.infer;