Archived
M3(2/5): human-tool 与 release —— 卡协议与放行门
human-tool(L3): - 三规矩机检:调用必附上下文(裸调用拒发)/ 回传即留痕 / 完成只出自放行通过 - 工单两模式 + 对话卡族的形态校验与回传校验;驳回必附意见;补全卡「没法填」旁路; 决策卡自定义收尾项必附理由;已答不得再答(只增不改) - 到达面两路(负责人 → 任务对话流;他人 → 系统消息);驳回重答 = 新卡实例、原卡闭合 - 调人预算由基座在环外强制;落地失败卡 = 执行工单的 preset release(L4): - 落地预检五项(入参 schema / 工具可用与授权 / 连接可达 / 目标预校验 / 数据冲突) - 预检全过才发验收工单,卡面即已验证的动作清单;不过即打回环内,不发卡 - 放行写合并 + 幂等落地(任务 × 意向);落地收敛才发射输出口; 受阻走落地失败卡且 emittedPorts 恒空;已成动作留存不回滚 215 个测试通过,门禁五道全绿 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
146e9bbfad
commit
561a2474ae
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* module: human-tool —— 对外类型(编码规范 §10.1)。
|
||||
*
|
||||
* **双层边界**(§9):范式只在引擎层;本模块是前端呈现协议的后端面 ——
|
||||
* 工单两模式(执行 / 验收)+ 对话卡族(决策 / 补全 / 评价)。
|
||||
* 落地失败卡是**执行工单的系统预置形态**,不构成第五种卡(OS 7.3)。
|
||||
*/
|
||||
import type { AcceptanceWorkOrder, ChatCard, ChatCardReply, ExecutionWorkOrder, StaffId, TaskId, WorkOrder, WorkOrderReply } from '@contracts';
|
||||
import type { ArrivalSurface, CardViolation } from './internal/domain/card-rules.ts';
|
||||
export type { AcceptanceWorkOrder, ArrivalSurface, CardViolation, ChatCard, ChatCardReply, ExecutionWorkOrder, WorkOrder, WorkOrderReply, };
|
||||
export declare class BareCallError extends Error {
|
||||
readonly violations: readonly CardViolation[];
|
||||
constructor(violations: readonly CardViolation[]);
|
||||
}
|
||||
export declare class ReplyRejectedError extends Error {
|
||||
readonly violations: readonly CardViolation[];
|
||||
constructor(violations: readonly CardViolation[]);
|
||||
}
|
||||
export declare class HumanCallBudgetExceededError extends Error {
|
||||
readonly taskId: TaskId;
|
||||
readonly used: number;
|
||||
readonly cap: number;
|
||||
constructor(taskId: TaskId, used: number, cap: number);
|
||||
}
|
||||
/** 工单草稿:id / at 由基座补齐 —— ID 生成不外泄给调用方(domain 零随机) */
|
||||
export type ExecutionWorkOrderDraft = Omit<ExecutionWorkOrder, 'mode' | 'done' | 'id' | 'at'> & {
|
||||
readonly id?: ExecutionWorkOrder['id'] | undefined;
|
||||
readonly at?: string | undefined;
|
||||
};
|
||||
export type AcceptanceWorkOrderDraft = Omit<AcceptanceWorkOrder, 'mode' | 'done' | 'id' | 'at'> & {
|
||||
readonly id?: AcceptanceWorkOrder['id'] | undefined;
|
||||
readonly at?: string | undefined;
|
||||
};
|
||||
/** 发卡的公共入参 */
|
||||
export type IssueContext = {
|
||||
readonly taskId: TaskId;
|
||||
/** 任务负责人 —— 决定到达面两路 */
|
||||
readonly ownerId: StaffId;
|
||||
/** 收件人;缺省即负责人 */
|
||||
readonly recipientId?: StaffId;
|
||||
/** 契约的调人预算(打扰上限) */
|
||||
readonly humanCallCap: number;
|
||||
};
|
||||
export type IssuedWorkOrder = {
|
||||
readonly order: WorkOrder;
|
||||
readonly arrival: ArrivalSurface;
|
||||
/** 到达面 = system-message 时的消息 ID(跨人卡) */
|
||||
readonly messageId?: string;
|
||||
};
|
||||
export type IssuedChatCard = {
|
||||
readonly card: ChatCard;
|
||||
readonly arrival: ArrivalSurface;
|
||||
readonly messageId?: string;
|
||||
};
|
||||
export type ReplyOutcome = {
|
||||
readonly order: WorkOrder;
|
||||
/** 完成只出自放行通过 —— 这里为真,任务才可收敛终态 */
|
||||
readonly producesCompletion: boolean;
|
||||
/** 驳回:环内回转,任务继续 */
|
||||
readonly rejection?: {
|
||||
readonly kind: 'redo' | 'revise';
|
||||
readonly comment: string;
|
||||
};
|
||||
/** 选择组的出口选择 —— 随放行一并确认(AM 1.2) */
|
||||
readonly selectedPorts: Readonly<Record<string, string>>;
|
||||
};
|
||||
export type HumanToolService = {
|
||||
/** 发执行工单;裸调用拒发,预算触顶熔断。id / at 留空即由基座生成 */
|
||||
issueExecution(context: IssueContext, order: ExecutionWorkOrderDraft): Promise<IssuedWorkOrder>;
|
||||
/** 发验收工单 —— 发卡**前**必须已过落地预检(由 release 保证,见其 CONTRACT) */
|
||||
issueAcceptance(context: IssueContext, order: AcceptanceWorkOrderDraft): Promise<IssuedWorkOrder>;
|
||||
/** 落地失败卡:执行工单的系统预置形态,不是第五种卡 */
|
||||
issueLandingFailure(context: IssueContext, input: {
|
||||
readonly failures: ReadonlyArray<{
|
||||
label: string;
|
||||
reason: string;
|
||||
}>;
|
||||
}): Promise<IssuedWorkOrder>;
|
||||
issueChatCard(context: IssueContext, card: ChatCard): Promise<IssuedChatCard>;
|
||||
replyWorkOrder(reply: WorkOrderReply, by: StaffId): Promise<ReplyOutcome>;
|
||||
replyChatCard(reply: ChatCardReply, by: StaffId): Promise<ChatCard>;
|
||||
/** 驳回重答:新卡实例,原卡闭合 —— 只增不改 */
|
||||
rejectAndReask(cardId: string, reason: string): Promise<IssuedChatCard>;
|
||||
openWorkOrders(taskId: TaskId): Promise<WorkOrder[]>;
|
||||
chatCards(taskId: TaskId): Promise<ChatCard[]>;
|
||||
/** org 停用前置检查要问的「待答卡」 */
|
||||
openCardsOf(staffId: StaffId): Promise<Array<{
|
||||
cardId: string;
|
||||
title: string;
|
||||
}>>;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* module: human-tool —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。
|
||||
*/
|
||||
export type { AcceptanceWorkOrder, AcceptanceWorkOrderDraft, ArrivalSurface, CardViolation, ChatCard, ChatCardReply, ExecutionWorkOrder, ExecutionWorkOrderDraft, HumanToolService, IssueContext, IssuedChatCard, IssuedWorkOrder, ReplyOutcome, WorkOrder, WorkOrderReply, } from './contract.ts';
|
||||
export { BareCallError, HumanCallBudgetExceededError, ReplyRejectedError } from './contract.ts';
|
||||
export type { CardStore, Clock, HumanEventPublisher, IdGenerator, MemoryRecorder, MessageOutbox } from './port.ts';
|
||||
export type { HumanToolServiceDeps } from './internal/usecase/human-tool-service.ts';
|
||||
export { createHumanToolService } from './internal/usecase/human-tool-service.ts';
|
||||
export { allCriteriaGreen, ARRIVAL_SURFACES, arrivalOf, checkChatCard, checkChatCardReply, checkWorkOrder, checkWorkOrderReply, producesCompletion, } from './internal/domain/card-rules.ts';
|
||||
export { createCardStore } from './internal/adapter/card-store.ts';
|
||||
export { chatCardTable, workOrderTable } from './internal/adapter/human-tool-schema.ts';
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* module: human-tool —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。
|
||||
*/
|
||||
import type { ChatCard, StaffId, TaskId, WorkOrder } from '@contracts';
|
||||
export type Clock = {
|
||||
now(): Date;
|
||||
};
|
||||
export type IdGenerator = {
|
||||
next(): string;
|
||||
};
|
||||
export type CardStore = {
|
||||
saveWorkOrder(taskId: TaskId, order: WorkOrder, recipientId: StaffId): Promise<void>;
|
||||
getWorkOrder(id: string): Promise<{
|
||||
taskId: TaskId;
|
||||
order: WorkOrder;
|
||||
recipientId: StaffId;
|
||||
} | undefined>;
|
||||
listWorkOrders(taskId: TaskId, openOnly: boolean): Promise<WorkOrder[]>;
|
||||
markWorkOrderDone(id: string, done: NonNullable<WorkOrder['done']>): Promise<void>;
|
||||
saveChatCard(taskId: TaskId, card: ChatCard, recipientId: StaffId): Promise<void>;
|
||||
getChatCard(id: string): Promise<{
|
||||
taskId: TaskId;
|
||||
card: ChatCard;
|
||||
recipientId: StaffId;
|
||||
} | undefined>;
|
||||
listChatCards(taskId: TaskId): Promise<ChatCard[]>;
|
||||
updateChatCard(card: ChatCard): Promise<void>;
|
||||
closeChatCard(id: string, supersededBy: string, reason: string): Promise<void>;
|
||||
/** 调人次数 —— 打扰率与预算的计数面 */
|
||||
humanCallCount(taskId: TaskId): Promise<number>;
|
||||
openCardsOf(staffId: StaffId): Promise<Array<{
|
||||
cardId: string;
|
||||
title: string;
|
||||
}>>;
|
||||
};
|
||||
/** 跨人卡的到达面 —— app 注入 `message`(L2) */
|
||||
export type MessageOutbox = {
|
||||
sendElicitation(input: {
|
||||
staffId: StaffId;
|
||||
title: string;
|
||||
body: string;
|
||||
cardId: string;
|
||||
sourceTaskId: string;
|
||||
payload: Record<string, unknown>;
|
||||
}): Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
};
|
||||
/** 回传即留痕 —— app 注入 `memory`(L2) */
|
||||
export type MemoryRecorder = {
|
||||
append(input: {
|
||||
taskId: TaskId;
|
||||
kind: 'card-sent' | 'card-reply' | 'release-verdict';
|
||||
title: string;
|
||||
detail?: Record<string, unknown>;
|
||||
}): Promise<void>;
|
||||
};
|
||||
/** 人事件 —— app 注入 `event`(L1):回传经统一入口写回任务 */
|
||||
export type HumanEventPublisher = {
|
||||
publishHumanEvent(input: {
|
||||
taskId: TaskId;
|
||||
cardId: string;
|
||||
payload: Record<string, unknown>;
|
||||
}): Promise<void>;
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* module: release —— 对外类型(编码规范 §10.1)。
|
||||
*
|
||||
* 放行门:草稿态 → 事实态的**唯一通道**。门归系统,启门归人(OS 10)。
|
||||
*/
|
||||
import type { ActionIntent, StaffId, TaskId } from '@contracts';
|
||||
import type { CriterionCheck, DeliverableCheck, LandingOutcome, LandingVerdict, PrecheckFinding, PrecheckItem, PrecheckReport } from './internal/domain/precheck.ts';
|
||||
export type { ActionIntent, CriterionCheck, DeliverableCheck, LandingOutcome, LandingVerdict, PrecheckFinding, PrecheckItem, PrecheckReport, };
|
||||
export declare class PrecheckFailedError extends Error {
|
||||
readonly report: PrecheckReport;
|
||||
constructor(report: PrecheckReport);
|
||||
}
|
||||
export declare class DeliverablesMissingError extends Error {
|
||||
readonly missing: readonly DeliverableCheck[];
|
||||
constructor(missing: readonly DeliverableCheck[]);
|
||||
}
|
||||
export type SubmitForReleaseInput = {
|
||||
readonly taskId: TaskId;
|
||||
readonly ownerId: StaffId;
|
||||
readonly humanCallCap: number;
|
||||
readonly deliverableId: string;
|
||||
readonly title: string;
|
||||
readonly basis: ReadonlyArray<{
|
||||
k: string;
|
||||
v: string;
|
||||
}>;
|
||||
readonly deliverables: readonly DeliverableCheck[];
|
||||
readonly criteria: readonly CriterionCheck[];
|
||||
/** 报告可修订:AI 拟值已填的表单字段 */
|
||||
readonly fields?: ReadonlyArray<Record<string, unknown>>;
|
||||
};
|
||||
export type SubmitOutcome = {
|
||||
readonly report: PrecheckReport;
|
||||
/** 预检全过才发卡;这里是发出的验收工单 ID */
|
||||
readonly workOrderId: string;
|
||||
};
|
||||
export type LandInput = {
|
||||
readonly taskId: TaskId;
|
||||
readonly ownerId: StaffId;
|
||||
readonly humanCallCap: number;
|
||||
readonly selectedPorts: Readonly<Record<string, string>>;
|
||||
};
|
||||
export type LandOutcome = {
|
||||
readonly verdict: LandingVerdict;
|
||||
/** 落地收敛后才发射的输出口 */
|
||||
readonly emittedPorts: readonly string[];
|
||||
readonly ledgerRowsMerged: number;
|
||||
/** 落地受阻时发出的落地失败卡 */
|
||||
readonly failureWorkOrderId?: string;
|
||||
};
|
||||
export type ReleaseService = {
|
||||
/** 登记写类动作意向(入参冻结,存入草稿态) */
|
||||
registerIntent(taskId: TaskId, intent: Omit<ActionIntent, 'taskId' | 'state'>): Promise<ActionIntent>;
|
||||
listIntents(taskId: TaskId): Promise<ActionIntent[]>;
|
||||
/** 预检 → 全过才发验收工单;不过即抛 `PrecheckFailedError`,打回环内修正 */
|
||||
submitForRelease(input: SubmitForReleaseInput): Promise<SubmitOutcome>;
|
||||
/** 只跑预检不发卡 —— 环内随时可看「还差什么」 */
|
||||
precheck(taskId: TaskId): Promise<PrecheckReport>;
|
||||
/** 放行通过即刻落地:草稿合并 + 动作执行(附幂等键)→ 收敛才发射输出口 */
|
||||
land(input: LandInput): Promise<LandOutcome>;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* module: release —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。
|
||||
*/
|
||||
export type { ActionIntent, CriterionCheck, DeliverableCheck, LandInput, LandOutcome, LandingOutcome, LandingVerdict, PrecheckFinding, PrecheckItem, PrecheckReport, ReleaseService, SubmitForReleaseInput, SubmitOutcome, } from './contract.ts';
|
||||
export { DeliverablesMissingError, PrecheckFailedError } from './contract.ts';
|
||||
export type { CardGateway, Clock, IdGenerator, IntentStore, LedgerGateway, MemoryRecorder, PortEmitter, TaskContext, ToolGateway, } from './port.ts';
|
||||
export type { ReleaseServiceDeps } from './internal/usecase/release-service.ts';
|
||||
export { createReleaseService } from './internal/usecase/release-service.ts';
|
||||
export { idempotencyKey, landingVerdict, missingDeliverables, PRECHECK_ITEM_LABEL, PRECHECK_ITEMS, submittable, summarize, } from './internal/domain/precheck.ts';
|
||||
export { createIntentStore } from './internal/adapter/intent-store.ts';
|
||||
export { actionIntentTable } from './internal/adapter/release-schema.ts';
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* module: release —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。
|
||||
*/
|
||||
import type { ActionIntent, StaffId, TaskId } from '@contracts';
|
||||
import type { LandingOutcome } from './internal/domain/precheck.ts';
|
||||
export type Clock = {
|
||||
now(): Date;
|
||||
};
|
||||
export type IdGenerator = {
|
||||
next(): string;
|
||||
};
|
||||
export type IntentStore = {
|
||||
save(intent: ActionIntent): Promise<void>;
|
||||
list(taskId: TaskId): Promise<ActionIntent[]>;
|
||||
setState(id: string, state: ActionIntent['state'], error?: string): Promise<void>;
|
||||
};
|
||||
/** 台账 —— app 注入 `ledger`(L1);放行写与冲突检查都在它那 */
|
||||
export type LedgerGateway = {
|
||||
checkConflicts(taskId: TaskId): Promise<Array<{
|
||||
field: string;
|
||||
key: Record<string, string | number>;
|
||||
}>>;
|
||||
releaseWrite(taskId: TaskId): Promise<{
|
||||
mergedRows: number;
|
||||
emittedEvents: number;
|
||||
}>;
|
||||
};
|
||||
/** 工具 —— app 注入 `toolbox`(L2);预检的②③④项与落地执行都经它 */
|
||||
export type ToolGateway = {
|
||||
/** 工具可用(未停用)与授权有效 */
|
||||
isInvocable(tool: string, activityId: string): Promise<{
|
||||
ok: boolean;
|
||||
reason?: string;
|
||||
}>;
|
||||
/** 入参 schema 校验 */
|
||||
validateInput(tool: string, input: Record<string, unknown>): Promise<{
|
||||
ok: boolean;
|
||||
errors: string[];
|
||||
}>;
|
||||
/** 目标预校验:工具契约含 validate 能力时调之(ADR-005) */
|
||||
validateTarget(input: {
|
||||
tool: string;
|
||||
activityId: string;
|
||||
taskId: TaskId;
|
||||
payload: Record<string, unknown>;
|
||||
}): Promise<{
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
} | undefined>;
|
||||
/** 落地执行 —— 附幂等键 */
|
||||
execute(input: {
|
||||
tool: string;
|
||||
activityId: string;
|
||||
taskId: TaskId;
|
||||
payload: Record<string, unknown>;
|
||||
idempotencyKey: string;
|
||||
}): Promise<LandingOutcome>;
|
||||
};
|
||||
/** 人类工具 —— app 注入 `human-tool`(L3):发验收工单与落地失败卡 */
|
||||
export type CardGateway = {
|
||||
issueAcceptance(input: {
|
||||
taskId: TaskId;
|
||||
ownerId: StaffId;
|
||||
humanCallCap: number;
|
||||
title: string;
|
||||
basis: ReadonlyArray<{
|
||||
k: string;
|
||||
v: string;
|
||||
}>;
|
||||
deliverable: string;
|
||||
criteria: ReadonlyArray<{
|
||||
label: string;
|
||||
level: 'L1' | 'L2' | 'L3' | 'L4' | 'L5';
|
||||
pass: boolean;
|
||||
}>;
|
||||
actions: ReadonlyArray<{
|
||||
label: string;
|
||||
effect: 'read' | 'write';
|
||||
detail: string;
|
||||
}>;
|
||||
}): Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
issueLandingFailure(input: {
|
||||
taskId: TaskId;
|
||||
ownerId: StaffId;
|
||||
humanCallCap: number;
|
||||
failures: ReadonlyArray<{
|
||||
label: string;
|
||||
reason: string;
|
||||
}>;
|
||||
}): Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
};
|
||||
/** 任务上下文 —— app 注入(活动 ID 与契约的输出口声明) */
|
||||
export type TaskContext = {
|
||||
activityIdOf(taskId: TaskId): Promise<string>;
|
||||
outputPortsOf(taskId: TaskId): Promise<{
|
||||
mandatory: string[];
|
||||
selective: Array<{
|
||||
group: string;
|
||||
ports: string[];
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
/** 输出口发射 —— app 注入 `event`(L1):落地收敛**之后**才发 */
|
||||
export type PortEmitter = {
|
||||
emit(input: {
|
||||
taskId: TaskId;
|
||||
port: string;
|
||||
payload: Record<string, unknown>;
|
||||
}): Promise<void>;
|
||||
};
|
||||
export type MemoryRecorder = {
|
||||
append(input: {
|
||||
taskId: TaskId;
|
||||
kind: 'action-intent' | 'release-verdict' | 'ledger-write';
|
||||
title: string;
|
||||
detail?: Record<string, unknown>;
|
||||
}): Promise<void>;
|
||||
};
|
||||
Reference in New Issue
Block a user