forked from ChaiHao/BPOS-MVP
@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
181 lines
7.9 KiB
TypeScript
181 lines
7.9 KiB
TypeScript
/**
|
|
* 契约七件(编码规范 §8.1;语义出处 OS 4.1,AM 01)。
|
|
*
|
|
* 契约是活动对调用方暴露的全部真相,也是对 Agent 机制的全部授权;
|
|
* 两种范式同一契约格式 —— 这是「统一套执行契约」的锚点。
|
|
* 契约必须在活动启动前冻结;运行中不得变更 —— 变更即新版本。
|
|
*
|
|
* ADR-003:**声明层 = JSON Schema 2020-12 子集**(活动声明是运行时数据,可 diff 可版本),
|
|
* 代码层 = zod。本文件用 zod 描述「声明的形状」,声明里的 schema 字段本身是 JSON Schema。
|
|
*/
|
|
import { z } from 'zod';
|
|
import { acceptanceLevelSchema } from './terminal.ts';
|
|
import { activityIdSchema, skillIdSchema } from './id.ts';
|
|
|
|
/** JSON Schema 2020-12 子集的运行时形态 —— 由 ajv 校验,不在编译期展开 */
|
|
export const jsonSchemaSchema = z.record(z.string(), z.unknown());
|
|
export type JsonSchema = z.infer<typeof jsonSchemaSchema>;
|
|
|
|
/* ── ① 输入口:单一入口,data + message ──────────────────────────────── */
|
|
|
|
export const contractInputSchema = z.object({
|
|
/** data:结构化载荷,按 schema 校验,缺项不得启动 */
|
|
data: jsonSchemaSchema,
|
|
/** message:委托消息(自然语言),可为空;**不得携带扩大授权的效力** */
|
|
messageAllowed: z.boolean().default(true),
|
|
messageHint: z.string().optional(),
|
|
});
|
|
export type ContractInput = z.infer<typeof contractInputSchema>;
|
|
|
|
/* ── ② 输出口:必发(AND)与选择组(XOR) ──────────────────────────── */
|
|
|
|
export const outputPortSchema = z.object({
|
|
name: z.string().min(1),
|
|
/** 交付物 schema —— 调用方仅凭口名与 schema 即可衔接下游 */
|
|
payload: jsonSchemaSchema,
|
|
description: z.string().optional(),
|
|
});
|
|
export type OutputPort = z.infer<typeof outputPortSchema>;
|
|
|
|
export const outputPortsSchema = z.object({
|
|
/** 必发口:每次「放行完成」必发;可多口,同时发射 */
|
|
mandatory: z.array(outputPortSchema).default([]),
|
|
/** 选择组:组内恰发一口;选择由环内决断,在放行(验收工单)上一并确认 */
|
|
selective: z
|
|
.array(
|
|
z.object({
|
|
group: z.string().min(1),
|
|
ports: z.array(outputPortSchema).min(2),
|
|
}),
|
|
)
|
|
.default([]),
|
|
});
|
|
export type OutputPorts = z.infer<typeof outputPortsSchema>;
|
|
|
|
/* ── ③ 必交付物 ─────────────────────────────────────────────────────── */
|
|
|
|
export const requiredDeliverableSchema = z.object({
|
|
key: z.string().min(1),
|
|
label: z.string(),
|
|
/** 缺一不得提交放行 —— 验收工单「成果清单」的来源 */
|
|
schema: jsonSchemaSchema.optional(),
|
|
});
|
|
export type RequiredDeliverable = z.infer<typeof requiredDeliverableSchema>;
|
|
|
|
/* ── ④ 验收判据:逐条标注真实等级 L1–L5 ───────────────────────────── */
|
|
|
|
export const acceptanceCriterionSchema = z.object({
|
|
key: z.string().min(1),
|
|
label: z.string(),
|
|
level: acceptanceLevelSchema,
|
|
/** L1/L2 的机检表达式(S-FEEL);L3–L5 由环内实证 / 独立评审 / 人放行承担 */
|
|
predicate: z.string().optional(),
|
|
/** loop 可引用业务模型的完成判据或 DMN 决策 */
|
|
modelRef: z.string().optional(),
|
|
});
|
|
export type AcceptanceCriterion = z.infer<typeof acceptanceCriterionSchema>;
|
|
|
|
/* ── ⑤ 禁区 ─────────────────────────────────────────────────────────── */
|
|
|
|
export const prohibitionSchema = z.object({
|
|
key: z.string().min(1),
|
|
/** 应短而硬;负面清单优于正面枚举 */
|
|
statement: z.string().min(1),
|
|
});
|
|
export type Prohibition = z.infer<typeof prohibitionSchema>;
|
|
|
|
/* ── ⑥ 授权边界 ─────────────────────────────────────────────────────── */
|
|
|
|
export const authorizationBoundarySchema = z.object({
|
|
/** AI 可自决的范围:金额 / 折扣 / 措辞等,以 S-FEEL 谓词或叙述给出 */
|
|
discretion: z.array(z.object({ key: z.string(), statement: z.string() })).default([]),
|
|
/** 预授权动作清单 —— 必须逐项列举,方可在过程中即时执行写类动作(OS 7.3) */
|
|
preauthorizedActions: z
|
|
.array(z.object({ tool: z.string(), operation: z.string(), limit: z.string().optional() }))
|
|
.default([]),
|
|
/** 调人预算(打扰上限) */
|
|
humanCallBudget: z.number().int().min(0),
|
|
});
|
|
export type AuthorizationBoundary = z.infer<typeof authorizationBoundarySchema>;
|
|
|
|
/* ── ⑦ 资源声明 ─────────────────────────────────────────────────────── */
|
|
|
|
/** 预算四类上限 —— 由基座在环外强制,不得由活动代码自查自守(AM 10.1) */
|
|
export const budgetCapsSchema = z.object({
|
|
steps: z.number().int().min(1),
|
|
tokens: z.number().int().min(1),
|
|
/** 活跃时长(分钟);挂起、监听等待、待卡回传不计入 */
|
|
activeMinutes: z.number().int().min(1),
|
|
humanCalls: z.number().int().min(0),
|
|
});
|
|
export type BudgetCaps = z.infer<typeof budgetCapsSchema>;
|
|
|
|
export const resourceDeclarationSchema = z.object({
|
|
/** 工具调用契约引用 + 版本约束 */
|
|
tools: z
|
|
.array(z.object({ name: z.string().min(1), version: z.string().optional() }))
|
|
.default([]),
|
|
skills: z.array(skillIdSchema).default([]),
|
|
/** 活动使用的 LLM,以自定义模型名(逻辑名)声明;活动不触厂商与密钥 */
|
|
models: z.array(z.string().min(1)).default([]),
|
|
/** 岗位:调人时卡投递到哪个工位 */
|
|
roles: z.array(z.string().min(1)).default([]),
|
|
budget: budgetCapsSchema,
|
|
});
|
|
export type ResourceDeclaration = z.infer<typeof resourceDeclarationSchema>;
|
|
|
|
/* ── 七件合体 ───────────────────────────────────────────────────────── */
|
|
|
|
export const sevenPartContractSchema = z.object({
|
|
activityId: activityIdSchema,
|
|
version: z.string().min(1),
|
|
input: contractInputSchema,
|
|
output: outputPortsSchema,
|
|
requiredDeliverables: z.array(requiredDeliverableSchema).default([]),
|
|
acceptanceCriteria: z.array(acceptanceCriterionSchema).default([]),
|
|
prohibitions: z.array(prohibitionSchema).default([]),
|
|
authorization: authorizationBoundarySchema,
|
|
resources: resourceDeclarationSchema,
|
|
});
|
|
export type SevenPartContract = z.infer<typeof sevenPartContractSchema>;
|
|
|
|
/**
|
|
* 完成时的发射集合 = 全部必发口 + 每个选择组各一口(AM 1.2)。
|
|
* 纯函数,前后端同源:前端预演、后端裁决。
|
|
*/
|
|
export function emissionPortNames(
|
|
ports: OutputPorts,
|
|
selected: Readonly<Record<string, string>>,
|
|
): string[] {
|
|
const out = ports.mandatory.map((p) => p.name);
|
|
for (const group of ports.selective) {
|
|
const choice = selected[group.group];
|
|
if (choice !== undefined) out.push(choice);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** 选择组必须恰选一口 —— 未选或选了组外的口即非法 */
|
|
export function validateSelection(
|
|
ports: OutputPorts,
|
|
selected: Readonly<Record<string, string>>,
|
|
): string[] {
|
|
const errors: string[] = [];
|
|
for (const group of ports.selective) {
|
|
const choice = selected[group.group];
|
|
if (choice === undefined) {
|
|
errors.push(`选择组「${group.group}」未选出口`);
|
|
continue;
|
|
}
|
|
if (!group.ports.some((p) => p.name === choice)) {
|
|
errors.push(`选择组「${group.group}」的出口「${choice}」不在组内`);
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
/** 验收等级不得谎报:L4 不得标注为 L1–L3;生成者不得自评(AM 12.1) */
|
|
export function machineCheckable(criterion: AcceptanceCriterion): boolean {
|
|
return criterion.level === 'L1' || criterion.level === 'L2';
|
|
}
|