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:
JCAI Main Agent
2026-07-27 01:28:49 +08:00
co-authored by Claude Opus 5
parent 146e9bbfad
commit 561a2474ae
31 changed files with 4194 additions and 0 deletions
+61
View File
@@ -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>;
};
+11
View File
@@ -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
View File
@@ -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>;
};