/** * 活动包清单六区与资产形状(编码规范 §8.1;语义出处 OS 11.1,AM 11)。 * * 清单机器可读,系统**不执行代码即知包为何物** —— 导入登记、安装静态审、编排装配读的都是清单。 * 资产与包分离版本化(OS 11.3):包版本管代码 / 契约 / 交互模版 / Harness; * 资产版本管业务模型 / DMN / 技能 —— 包只携初始版,此后由进化管线在系统内产生。 */ import { z } from 'zod'; import { activityIdSchema, skillIdSchema } from './id.ts'; import { jsonSchemaSchema, sevenPartContractSchema } from './contract7.ts'; import { listenerSpecSchema } from './envelope.ts'; import { objectDeclarationSchema } from './ledger.ts'; /* ── ① 身份 ─────────────────────────────────────────────────────────── */ export const manifestIdentitySchema = z.object({ activityId: activityIdSchema, name: z.string().min(1), packageVersion: z.string().min(1), /** * 业务分类(一级,组织自定)—— 活动库唯一的一维。 * 「活动类型(标准/案例)」那一维 2026-07-29 撤销:活动不再分范式,**一律 loop**。 */ category: z.string().min(1), sdkVersion: z.string().min(1), checksum: z.string().min(1), signature: z.string().optional(), }); export type ManifestIdentity = z.infer; /* ── ③ 声明 ─────────────────────────────────────────────────────────── */ export const harnessConfigSchema = z.object({ /** 记忆窗口:每次 LLM 调用加载多少轮会话历史 —— 活动侧唯一记忆接口(AM 09) */ memoryWindow: z.number().int().min(1).max(200).default(20), /** 停滞检测:多久无推进算空转(ISO-8601 duration) */ stallAfter: z.string().min(2).default('PT30M'), /** 时限阶梯 */ nudge: z.array(z.object({ after: z.string().min(2), action: z.enum(['nudge', 'escalate']) })).default([]), /** 步失败的默认重试(flow 每步可覆盖) */ retry: z.object({ times: z.number().int().min(0).max(10).default(2), backoff: z.string().default('PT30S') }).default({ times: 2, backoff: 'PT30S', }), }); export type HarnessConfig = z.infer; export const manifestDeclarationsSchema = z.object({ /** 模型声明:自定义模型名(逻辑名);安装静态审核对名称已绑定 */ models: z.array(z.string().min(1)).default([]), /** 工具声明:调用契约引用 + 版本约束 */ tools: z.array(z.object({ name: z.string().min(1), version: z.string().optional() })).default([]), /** 台账与共享域使用声明(域.对象.字段) */ ledger: z.array(objectDeclarationSchema).default([]), /** 静态监听声明 —— 时限必配,悬空监听安装时拒绝 */ listeners: z.array(listenerSpecSchema).default([]), harness: harnessConfigSchema, }); export type ManifestDeclarations = z.infer; /* ── ④ 资产初始版 ───────────────────────────────────────────────────── */ /* loop 的业务模型:CMMN XML + 案例说明书 + DMN(AM 03) */ export const dmnDecisionTableSchema = z.object({ id: z.string().min(1), label: z.string(), hitPolicy: z.enum(['UNIQUE', 'FIRST', 'PRIORITY', 'COLLECT']).default('FIRST'), inputs: z.array(z.object({ name: z.string().min(1), expression: z.string().min(1) })).min(1), outputs: z.array(z.object({ name: z.string().min(1) })).min(1), rules: z .array( z.object({ id: z.string().min(1), /** 单元格谓词:S-FEEL(空串 = 「-」任意匹配) */ when: z.array(z.string()), then: z.array(z.unknown()), annotation: z.string().optional(), }), ) .min(1), }); export type DmnDecisionTable = z.infer; export const dmnModelSchema = z.object({ /** DRD:声明「定什么需要知道什么」—— 决策 → 依赖的决策 / 输入项 */ drd: z.array( z.object({ decision: z.string().min(1), requires: z.array(z.string()).default([]), inputs: z.array(z.string()).default([]), }), ), tables: z.array(dmnDecisionTableSchema), }); export type DmnModel = z.infer; /** * **步骤剧本** —— 一个计划项一条,把「这一步是什么状态」写成机器读得懂的东西。 * * 为什么要有它(2026-07-31 加,起因是 A1 反复空转): * 从前每一拍喂给模型的是**整本说明书 + 全部工具 + 全部可写台账**, * 模型得自己从六步里认出「我现在在第几步」。认对了还好,认岔了就开始转圈 —— * 实测 S1 已经把八字段写齐了,而说明书里 S3 写着「把八字段补到齐」, * 模型于是把同样三行又写一遍,被「写了等于没写」拒回,下一拍再写一遍,如此往复。 * * 治法不是把说明书写得更啰嗦,是**把状态挪进引擎**: * ① `doneWhen` 让引擎自己算得出「这一步的成果已经在那儿了」—— 是就直接收掉, * **不问模型**(能算的不问 LLM:同输入必同结论、零 token); * ② `guide` / `writes` / `tools` 让每一拍的上下文**只装当前这一步要用的东西** —— * 上下文小了,模型跑偏的余地也就小了。 * * 不写剧本的活动照旧跑(整本说明书 + 全部工具),只是没有这两样好处。 */ export const planStepSchema = z.object({ /** 对应 CMMN 里的计划项 id —— 对不上的条目安装期即拒 */ item: z.string().min(1), /** 一句话:这一步交出什么 */ intent: z.string().min(1), /** 这一步的做法(从说明书里搬过来的那一段)—— 只在轮到它时进上下文 */ guide: z.string().min(1), /** 这一步可写哪几个台账地址「域.对象」;空 = 这一步不写台账 */ writes: z.array(z.string()).default([]), /** * 这一步**要读哪几个台账地址** —— 与 `writes` 对称,同样是「域.对象」。 * * 为什么非声明不可:`域.对象` 整表那一段进提示词前会**按字符截断** * (整表里躺着历史所有已放行单的行,只会越来越长,不截会把上下文吃光)。 * 截断是盲的 —— 砍掉的可能恰是这一步要用的那张表。实测:A1 第一步要拿发件邮箱 * 去 \`customer.contact\` 认客户,而这一单的来函正文与 BOM 附件就占了近 2000 字, * 客户联系人表整个落在刀口外;模型看不见,只能报「主数据里没找到」, * 于是老客户被当成新客户 —— **而库里那一行一直好端端在**。 * * 声明了的表**排在最前且不参与截断**:这一步该看见的,一定看得见。 */ reads: z.array(z.string()).default([]), /** * 这一步的**工作区画面**(视图模版名)—— 人办这一步时看的那份件。 * * 不写就由模型临场挑。而「这一步该看哪张」是**写清单时就定死的事**: * 阶段二派执行单,人要看的是那份询价受理需求;模型挑了「缺口清单」, * 人拿到的就不是那份件了(实测:两份目标件都在包里,推出来的却是旧的那张)。 * 画面归活动定,不归模型临场发挥 —— 模型仍可用 `say.view` 换画面, * 但**没换的时候按这里推**,而不是不推或推上一张。 */ view: z.string().optional(), /** 这一步可调哪几件工具;空 = 这一步不调工具 */ tools: z.array(z.string()).default([]), /** * **这一步算干完了没有** —— 对任务视图求值的 S-FEEL 谓词。 * * 真了引擎就自己把这一项收掉,不问模型。所以它必须是「成果已存在」的判据, * 不是「该不该做」的判据:写成 `missing_key_total = 0` 这种**开局就为真**的式子, * 会让那一步在还没干之前就被收掉。 * * **`X != null` 是个坑**:S-FEEL 里未绑定的名字 `!= null` 求值为 **true**, * 写它等于让那一步开局即收。要判「这个键写过没有」,用正向式子(`X >= 0`); * 布尔列更要绕开 —— 它合法值含 false,拿它分不清「没写」与「写了 false」, * 该改判同一步写下的计数列。 */ doneWhen: z.string().optional(), /** * **这一步派的卡穿哪副面孔**(2026-08-27)—— 由剧本声明、引擎决定,不靠模型抄卡骨架。 * `data` = 执行工单摆成数据工单;`review` = 决策卡摆成验收工单(中间审核,可循环;终局验收仍是 submit)。 * * 起因:说明书的卡骨架里写着 `"variant":"data"`,模型抄卡时把它丢了(看着像元数据), * 对话区于是照旧摆「执行工单」。与 `view` 同一条道理:这一步派的是什么单,是写剧本时就定死的事, * 归活动声明、不归模型临场发挥;模型写不写都一样。 */ cardVariant: z.enum(['data', 'review']).optional(), /** * **人工验收清单**(2026-08-27 用户定):这一步派出的验收卡(终局 `submit` 的验收工单,或 * `cardVariant:'review'` 的审核卡)上,人要走的核对步骤 —— 一条一句可核对的动作,对每份验收工单**固定**。 * 不是系统判的判据(那是 `acceptanceCriteria`,把提交门);是人拿着这份工单要亲自核什么。 * 卡上一条一枚「达标 / 不达标」开关,全部达标才能「确认通过」。 */ acceptanceChecklist: z.array(z.string().min(1)).optional(), }); export type PlanStep = z.infer; /** * 阶段 —— **给人看的进度刻度**,引擎一眼都不看。 * * 计划项是给引擎的:一件事拆到能判「干完没有」的粒度,A1 有六项。 * 可六格进度条对人没有意义 —— 「查历史价 / 打标 / 定去向」在业务上是**一件事** * (判这单该怎么走),人关心的是「到哪一步了」,不是引擎拆了几项。 * 所以另立一层:几个计划项合成一个阶段,进度条按阶段走。 * * **只声明激活态的阶段**。结束态不在这里 —— 每个活动都以放行收口(铁律③), * 那是系统的规矩不是活动的阶段;进度条自己在末尾加这一格。 */ export const casePhaseSchema = z.object({ /** 阶段名 —— 进度条上显示的就是它 */ name: z.string().min(1), /** 这一阶段含哪几个计划项(CMMN item id);**全收完** = 这一阶段过了 */ items: z.array(z.string().min(1)).min(1), }); export type CasePhase = z.infer; export const loopModelSchema = z.object({ /** 案例模型 · 机器面:CMMN XML,语义引擎以它为运行结构 */ cmmnXml: z.string().min(1), /** 案例模型 · 叙事面:说明书,与 XML **同版本发布,不允许分叉**(AM 12.2) */ narrative: z.string().min(1), dmn: dmnModelSchema, /** 步骤剧本:一个计划项一条(见 `planStepSchema`);**不写照旧跑**(整本说明书 + 全部工具) */ steps: z.array(planStepSchema).optional(), /** 阶段:进度条的刻度(见 `casePhaseSchema`);**不写就没有进度条**,不编 */ phases: z.array(casePhaseSchema).optional(), }); export type LoopModel = z.infer; /** * 业务模型只有一种(2026-07-29 用户定:活动不再分 flow 和 loop,内部实现都是 Loop)—— * 案例模型 CMMN + 说明书 + DMN。步序声明(flow)连同它的引擎一并退役: * 一件事该怎么办,由 Agent 依案例模型临场决定,不再有「照谱执行」这一路。 */ export const businessModelSchema = loopModelSchema; export type BusinessModel = z.infer; /* 技能(AM 08):载方法不载权力 */ export const skillSchema = z.object({ id: skillIdSchema, label: z.string().min(1), /** 何时适用 —— 常驻上下文只有目录,按事加载正文 */ applicability: z.string().min(1), /** 怎么判断、怎么编排工具 */ method: z.string().min(1), /** 结晶片段脚本:反复出现的确定性子序列;按纯工具规矩注册留痕 */ crystallized: z .array(z.object({ tool: z.string().min(1), input: z.record(z.string(), z.string()) })) .default([]), knowledgeRefs: z.array(z.string()).default([]), version: z.string().min(1), }); export type Skill = z.infer; /** `域.对象` 两段地址(与 `ledgerAddressSchema` 同一套标识符规则,只是写成一串) */ const viewLedgerAddress = z.string().regex(/^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/, '台账地址写成 域.对象'); const viewColumn = z.string().regex(/^[a-z][a-z0-9_]*$/, '列名只准小写字母开头、字母数字下划线'); /** `case`(案号本身)或 `.<列>`(前面某个集合那一列的值) */ const viewDataRef = z.string().min(1); /** * 运营中心沙箱看板的**取数声明**(2026-08-27):模版归活动,「哪几行是这一单的」也归活动 —— 案号前缀、 * 哪张表按哪一列圈,平台不该写死;基座只照声明逐表查**正式表**、把行集摊成扁平作用域交渲染器。 * * 求值:先定案号 `case.prefix + 实例开案时刻 YYYYMMDDHHmmss`(与引擎注给模型铸主键的 `case:openedAt` 同一串); * `rows` 按序求值,每条 = `ledger` 里 `by.column ∈ values(by.from)` 的行(`from` 只能引用 `case` 或前面已求出的 * 集合),可再用 `not` 排除一批、`orderBy` 排序。作用域里 `` = 行数组、`.<列>` = 首行标量、 * `_rows` = 行数、`_<列>_<值>` = tally 计数。案号那一行查无 → 这一单没落账,看板如实说。 */ export const viewDataRowsSchema = z.object({ /** 作用域键(模版 `data-rows` 用它);与台账地址同名最省事,同一张表圈两批时另起名 */ as: z.string().min(1), /** 必须在 `declarations.ledger` 里 */ ledger: viewLedgerAddress, by: z.object({ column: viewColumn, from: viewDataRef }), not: z.object({ column: viewColumn, from: viewDataRef }).optional(), orderBy: z.array(z.object({ field: viewColumn, dir: z.enum(['asc', 'desc']) })).optional(), }); export type ViewDataRows = z.infer; export const viewDataSchema = z.object({ case: z.object({ ledger: viewLedgerAddress, key: viewColumn, prefix: z.string() }), rows: z.array(viewDataRowsSchema), }); export type ViewData = z.infer; /** `by.from` / `not.from` 里指案号本身的那个字 */ export const VIEW_DATA_CASE_REF = 'case'; export const viewTemplateSchema = z.object({ name: z.string().min(1), html: z.string(), /** 只有运营中心沙箱看板这类**按实例圈行**的模版才带;工单件不带(它们的行由引擎按草稿层给) */ data: viewDataSchema.optional(), }); export type ViewTemplate = z.infer; export const manifestAssetsSchema = z.object({ businessModel: businessModelSchema, skills: z.array(skillSchema).default([]), /** 视图 HTML 模版 —— 模版是内容不是代码,容器净化渲染、无脚本执行权 */ viewTemplates: z.array(viewTemplateSchema).default([]), /** 卡内容 schema */ cardSchemas: z.record(z.string(), jsonSchemaSchema).default({}), /** 蒸馏钩子:终态封卷 / 里程碑达成时从任务记忆归纳 */ distillHooks: z .array(z.object({ on: z.enum(['terminal', 'milestone']), milestone: z.string().optional(), prompt: z.string() })) .default([]), }); export type ManifestAssets = z.infer; /* ── ⑤ 样例集 ───────────────────────────────────────────────────────── */ export const sampleCaseSchema = z.object({ name: z.string().min(1), input: z.record(z.string(), z.unknown()), message: z.string().optional(), /** 期望输出口与载荷形状 —— 首装审与回归的依据 */ expect: z.object({ ports: z.array(z.string()).min(1), payloadShape: jsonSchemaSchema.optional(), }), }); export type SampleCase = z.infer; /* ── 清单六区合体 ───────────────────────────────────────────────────── */ export const activityManifestSchema = z.object({ identity: manifestIdentitySchema, contract: sevenPartContractSchema, declarations: manifestDeclarationsSchema, assets: manifestAssetsSchema, samples: z.array(sampleCaseSchema).min(1), /** ⑥ 代码入口:.JS 产物的入口与校验和 */ entry: z.object({ file: z.string().min(1), checksum: z.string().min(1) }), }); export type ActivityManifest = z.infer; /* ── 安装两审的报告形状(OS 11.2) ─────────────────────────────────── */ export const staticAuditFindingSchema = z.object({ code: z.enum([ 'signature-invalid', 'sdk-incompatible', 'schema-invalid', 'tool-contract-missing', 'model-name-unbound', 'dangling-listener', 'template-has-script', 'environment-capability', 'dynamic-eval', 'ledger-type-conflict', 'acceptance-level-misreported', 'acceptance-predicate-unbound', 'deliverable-address-unbound', 'ledger-kind-invalid', // refersTo 指向没声明的表/列 —— 引用检查会成「永远查无此值」的死墙(2026-08-24) 'reference-target-unbound', ]), severity: z.enum(['error', 'warn']), message: z.string(), at: z.string().optional(), }); export type StaticAuditFinding = z.infer; export const replayAuditReportSchema = z.object({ /** 「旧成不坏」是生效硬门;「旧败能成」为报告参考项,不作门槛 */ oldPassStillPass: z.boolean(), checked: z.number().int().min(0), regressions: z.array(z.object({ taskId: z.string(), detail: z.string() })).default([]), newlyBlocked: z.array(z.object({ taskId: z.string(), detail: z.string() })).default([]), /** 首次安装无历史:免回放审,以静态审 + 样例集替代 */ skippedNoHistory: z.boolean().default(false), }); export type ReplayAuditReport = z.infer; export const installVerdictSchema = z.object({ accepted: z.boolean(), staticFindings: z.array(staticAuditFindingSchema), replay: replayAuditReportSchema, /** * **这一版真的存进库了吗**(2026-08-27)—— 安装恒新增、同版重装是 no-op:两审过了、版本已在库, * 什么都没写。从前这一维不回,安装脚本照样打印「活动包已装」,改了内容忘升版本的人以为装上了 * (同一天两个会话共用一个库,各自升到同一个版本号,后装的那份就这么静默丢了两次)。 * `false` = 库里已有同版,本次未重装;老调用方不读它也不受影响。 */ stored: z.boolean().default(true), }); export type InstallVerdict = z.infer;