M2: 基座能力 —— memory / toolbox / channel / message

- memory   任务记忆四性质(只追加·信封盖戳·治理先行·归侧)、活动记忆四层与条目三件套、
           任务日志两级投影(结构化零 LLM / 叙述性小结不入事实库)、度量八指标只读投影
- toolbox  注册两审(静态审 + 试运行审)、效应等级决定运行时待遇、契约演进只增不破坏、
           白名单与纯工具私有作用域、ToolInvoker(两缝之二,live/record/replay)
- channel  通道实例配置、入站归一(解析 → 系统写 → 数据事件)、出站发送与关联记录登记
- message  通知 / 跨任务征答(就地作答 · 驳回重答只增不改)/ 死信 / 无主兜底

app:message 接上 event 的 MessageFallback;派发器按目标 ID 取 advisory lock 串行消费 +
消费幂等表;/api/ingest 模拟邮件通道(mock 只在外界)

已验证:无主 RFQ 邮件入站 → 归属解析 no-match → 事件落库 → 兜底落 OP 岗位的消息要素
门禁五道全绿;150 个单元测试 + 1 个 PG 集成测试

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 01:16:52 +08:00
co-authored by Claude Opus 5
parent 14d4fa046a
commit f0444f1417
73 changed files with 7488 additions and 31 deletions
+48
View File
@@ -0,0 +1,48 @@
/**
* module: channel —— 对外类型(编码规范 §10.1)。
*/
import type { InboundMail, OwnershipResolution, TaskId } from '@contracts';
import type { ChannelInstanceConfig, ExtractRule, LedgerMapping, NormalizedWrite } from './internal/domain/payload-mapping.ts';
export type { ChannelInstanceConfig, ExtractRule, InboundMail, LedgerMapping, NormalizedWrite, OwnershipResolution };
export declare class ChannelNotFoundError extends Error {
readonly channelId: string;
constructor(channelId: string);
}
export declare class ChannelDisabledError extends Error {
readonly channelId: string;
constructor(channelId: string);
}
export type InboundOutcome = {
readonly eventId: string;
readonly resolution: OwnershipResolution;
readonly writes: readonly NormalizedWrite[];
readonly listenersHit: number;
readonly triggersHit: number;
readonly fellBackToMessage: boolean;
};
export type OutboundMail = {
readonly taskId: TaskId;
readonly to: readonly string[];
readonly cc?: readonly string[];
readonly subject: string;
readonly body: string;
readonly threadId?: string;
readonly attachments?: ReadonlyArray<{
name: string;
ref: string;
}>;
};
export type ChannelService = {
list(): Promise<ChannelInstanceConfig[]>;
upsert(config: ChannelInstanceConfig): Promise<ChannelInstanceConfig>;
/**
* 入站:归属解析 → 归一为**系统写** → 数据事件。
* 「环外回音经归属解析归一为数据写入后,以数据事件抛出 —— 活动认数据,不认通道。」
*/
ingestMail(channelId: string, mail: InboundMail): Promise<InboundOutcome>;
/** 出站:触达世界的动作;发送即登记出站关联记录(归属解析①③级的对表来源) */
sendMail(channelId: string, mail: OutboundMail): Promise<{
messageId: string;
threadId: string;
}>;
};
+11
View File
@@ -0,0 +1,11 @@
/**
* module: channel —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。
*/
export type { ChannelInstanceConfig, ChannelService, ExtractRule, InboundMail, InboundOutcome, LedgerMapping, NormalizedWrite, OutboundMail, } from './contract.ts';
export { ChannelDisabledError, ChannelNotFoundError } from './contract.ts';
export type { AttachmentArchive, ChannelRepository, Clock, EventGateway, IdGenerator, LedgerSystemWriter, MailTransport, } from './port.ts';
export type { ChannelServiceDeps } from './internal/usecase/channel-service.ts';
export { createChannelService } from './internal/usecase/channel-service.ts';
export { extractFields, normalize } from './internal/domain/payload-mapping.ts';
export { createChannelRepository, createFileAttachmentArchive, createSimulatedMailTransport, } from './internal/adapter/channel-store.ts';
export { channelTable, outboundMailTable } from './internal/adapter/channel-schema.ts';
+94
View File
@@ -0,0 +1,94 @@
/**
* module: channel —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。
*/
import type { EnvelopeDraft, OwnershipResolution, PositionId, TaskId } from '@contracts';
import type { ChannelInstanceConfig } from './internal/domain/payload-mapping.ts';
export type Clock = {
now(): Date;
};
export type IdGenerator = {
next(): string;
};
export type ChannelRepository = {
list(): Promise<ChannelInstanceConfig[]>;
get(channelId: string): Promise<ChannelInstanceConfig | undefined>;
upsert(config: ChannelInstanceConfig): Promise<ChannelInstanceConfig>;
};
/** 事件入口 —— app 注入 `event`(L1)的出口 */
export type EventGateway = {
ingestInbound(input: {
facts: {
threadId?: string;
from: string;
to: readonly string[];
subject: string;
body: string;
receivedAt: Date;
};
rules: {
patterns: ReadonlyArray<{
key: string;
regex: string;
source: 'subject' | 'body' | 'both';
}>;
addressWindowMs: number;
fallbackPosition?: PositionId | undefined;
};
draft: EnvelopeDraft;
}): Promise<{
eventId: string;
resolution: OwnershipResolution;
plan: {
taskTargets: readonly string[];
triggerTargets: readonly string[];
messageFallback: boolean;
};
}>;
recordOutbound(ref: {
taskId: TaskId;
threadId?: string;
address: string;
sentAt: Date;
}): Promise<void>;
};
/**
* 台账系统写 —— app 注入 `ledger`(L1)的 `systemWrite`。
* 入站归一是「登记世界已发生的事实」,走**系统写**:不过放行门,写正式版并即时抛数据事件(OS 09)。
*/
export type LedgerSystemWriter = {
systemWrite(input: {
address: {
domain: string;
object: string;
};
key: Record<string, string | number>;
values: Record<string, unknown>;
cause: {
kind: 'inbound';
ref: string;
};
taskId?: TaskId;
}): Promise<void>;
};
/** 邮件收发 —— 外界。MVP 允许模拟通道 / 录制回放替身(README MVP 完成判据) */
export type MailTransport = {
send(input: {
readonly from: string;
readonly to: readonly string[];
readonly cc: readonly string[];
readonly subject: string;
readonly body: string;
readonly threadId?: string;
readonly attachments: ReadonlyArray<{
name: string;
ref: string;
}>;
}): Promise<{
messageId: string;
threadId: string;
}>;
};
/** 原件存档 —— 附件对象的存放;返回存档地址(信封的原件指针) */
export type AttachmentArchive = {
put(name: string, content: string): Promise<string>;
};