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:
JCAI Main Agent
2026-07-27 00:59:14 +08:00
co-authored by Claude Opus 5
parent 7cd664af3a
commit 14d4fa046a
120 changed files with 15236 additions and 78 deletions
@@ -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,
};
}