Archived
M1: 底座 —— @contracts 首版 + event / ledger / org / llm
@contracts 首版(§8.1 九个文件): - id 品牌 ID / terminal 终态五名与归侧四标签 / envelope 信封六字段与四类事件 - card 工单两模式 + 对话卡族(形状对齐 2026-07-26 原型定稿) - contract7 契约七件 / ledger 三级寻址与写三分 / process-package 流程包 DSL 与触发器三类 - manifest 活动包清单六区(含 CMMN·DMN·步序声明的资产形状)/ org / api 路由契约 模块(L1): - event 信封存储、归属解析三级短路、投递五诺、监听注册表、三级消费裁定 - ledger schema 自生长(最大化合并/最宽类型/只增不破坏)、任务视图叠加、写三分、冲突检查 - org 岗位授权(客体三类×动作三种)、停用前置检查、会话、种子播种 - llm 四元绑定、ModelGateway 三模式(live/record/replay)—— 两缝之一 app 装配:Hono 服务 + 鉴权中间件 + 治理台/工作台路由;drizzle 迁移;AES-256-GCM 凭证密封 ADR-007 工具链与门禁 · ADR-008 投递队列 · ADR-009 凭证加密 (ADR 编号自 007 续:编码规范 §14 已用到 006,README 写「从 005 续」是笔误) 门禁五道全绿;96 个测试通过,含对真实 PG 的 ledger 集成测试 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
co-authored by
Claude Opus 5
parent
7cd664af3a
commit
14d4fa046a
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 投递五诺的纯判定部分 —— 零 IO(编码规范 §5.3;语义出处 OS 6.3 / §7.1)。
|
||||
*
|
||||
* 先持久后投递 —— 信封写存储与入队**同一事务**(adapter 承担)
|
||||
* 同任务按序 —— 任务级串行消费(adapter 以 advisory lock 承担)
|
||||
* 至少一次+幂等 —— 消费侧幂等表(事件 ID × 消费者)唯一约束 + 本文件的去重判定
|
||||
* 处理有回执 —— 失败重投,超限入死信(本文件给出「该不该再投」)
|
||||
* 扇出与幂等 —— 同一事件命中多任务即多份独立投递,各带投递 ID;
|
||||
* 同一事件对同一触发器**至多起一单**,以事件 ID 为幂等键
|
||||
*/
|
||||
|
||||
export type RetryPolicy = {
|
||||
readonly maxAttempts: number;
|
||||
readonly backoffMs: readonly number[];
|
||||
};
|
||||
|
||||
export const DEFAULT_RETRY: RetryPolicy = { maxAttempts: 5, backoffMs: [1_000, 5_000, 30_000, 120_000] };
|
||||
|
||||
export type RetryDecision =
|
||||
| { readonly kind: 'retry'; readonly delayMs: number; readonly attempt: number }
|
||||
| { readonly kind: 'dead-letter'; readonly attempts: number };
|
||||
|
||||
export function decideRetry(attempt: number, policy: RetryPolicy = DEFAULT_RETRY): RetryDecision {
|
||||
if (attempt >= policy.maxAttempts) return { kind: 'dead-letter', attempts: attempt };
|
||||
const idx = Math.min(attempt - 1, policy.backoffMs.length - 1);
|
||||
return { kind: 'retry', delayMs: policy.backoffMs[Math.max(idx, 0)] ?? 1_000, attempt: attempt + 1 };
|
||||
}
|
||||
|
||||
/** 幂等键:事件 ID × 消费者 —— 消费侧唯一约束的形态 */
|
||||
export function consumptionKey(eventId: string, consumer: string): string {
|
||||
return `${eventId}::${consumer}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扇出:同一事件命中多个任务监听 → 每个命中任务一份独立投递(信封关联保持单目标)。
|
||||
* 同一任务命中多条监听只产生**一份**投递 —— 任务侧按事件 ID 幂等,重复投递无意义。
|
||||
*/
|
||||
export function fanOutTargets(hits: ReadonlyArray<{ taskId: string }>): string[] {
|
||||
return [...new Set(hits.map((h) => h.taskId))];
|
||||
}
|
||||
|
||||
/**
|
||||
* 三级消费的裁定(OS 6.3):
|
||||
* 任务监听与触发器**相互独立**判定 —— 同一事件可既推进旧单又起新单;
|
||||
* 消息兜底**只在**既无监听命中亦无触发命中时发生。
|
||||
*
|
||||
* 注:有主 ≠ 必须唤醒 —— 归属解析命中的任务,事件一律写入其任务数据与任务记忆(记录);
|
||||
* 是否推进由监听命中决定;零命中时仅记录,**不降级为无主、不入消息兜底**。
|
||||
*/
|
||||
export type ConsumptionPlan = {
|
||||
readonly taskTargets: readonly string[];
|
||||
readonly triggerTargets: readonly string[];
|
||||
readonly messageFallback: boolean;
|
||||
readonly recordOnlyTask: string | undefined;
|
||||
};
|
||||
|
||||
export function planConsumption(input: {
|
||||
readonly listenerHits: ReadonlyArray<{ taskId: string }>;
|
||||
readonly triggerHits: readonly string[];
|
||||
readonly ownedTaskId: string | undefined;
|
||||
}): ConsumptionPlan {
|
||||
const taskTargets = fanOutTargets(input.listenerHits);
|
||||
const triggerTargets = [...new Set(input.triggerHits)];
|
||||
const nothingHit = taskTargets.length === 0 && triggerTargets.length === 0;
|
||||
return {
|
||||
taskTargets,
|
||||
triggerTargets,
|
||||
// 有主但零监听命中 → 仅记录,不兜底
|
||||
messageFallback: nothingHit && input.ownedTaskId === undefined,
|
||||
recordOnlyTask:
|
||||
input.ownedTaskId !== undefined && !taskTargets.includes(input.ownedTaskId)
|
||||
? input.ownedTaskId
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user