Archived
harness 与工作台同日修:数据工单确认即入库 · 叫了人不再自动推进(两道结构闸)· 审核卡 = 终局验收卡结构 · 验收清单人点达标 · 迟到守卫不丢放行裁决 · 画面跟着这一拍发出去的卡走 · 成果详情只翻页 · 占位居中 · 进度条
human-tool / ledger / loop-engine / release / task / interaction 六模块与 app 层(desk 路由、engines、consumers、 data-order-landing、case-stamp、reset-runs、seed-activities、容器与 server 装配),web 的 desk-task (ViewRegion / chat-bubbles / review-face)与 app.css、ui-kit 图标;契约 card / manifest; 迁移 0034_task_seq 与迁移日志;api-report 快照;CLAUDE.md 快照、debt、活动开发规范、HTML 报告规范同步。 逐条口径见 CLAUDE.md 快照(2026-08-27 下午至晚)。另一会话做的,一直留在工作树没入库;与同批另两条提交互相依赖。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0167qWjeyBwvfdGovd7FWbCu
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
/** 沙箱看板的取数声明:形状、地址与列名的规矩;老清单(不带 data)照旧过 */
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { VIEW_DATA_CASE_REF, viewDataSchema, viewTemplateSchema } from '../index.ts';
|
||||
|
||||
const spec = {
|
||||
case: { ledger: 'sub_eval.verdict', key: 'eval_ref', prefix: 'W05-' },
|
||||
rows: [
|
||||
{ as: 'sub_eval.verdict', ledger: 'sub_eval.verdict', by: { column: 'eval_ref', from: VIEW_DATA_CASE_REF } },
|
||||
{ as: 'demand.request', ledger: 'demand.request', by: { column: 'request_id', from: 'sub_eval.verdict.request_id' } },
|
||||
{
|
||||
as: 'validation.history',
|
||||
ledger: 'validation.record',
|
||||
by: { column: 'validation_internal_pn', from: 'sub_eval.pn_check.internal_pn' },
|
||||
not: { column: 'request_id', from: 'sub_eval.verdict.request_id' },
|
||||
orderBy: [{ field: 'validation_id', dir: 'desc' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('viewDataSchema', () => {
|
||||
it('往返', () => {
|
||||
expect(viewDataSchema.parse(spec)).toEqual(spec);
|
||||
});
|
||||
it('地址要写成 域.对象,列名只准标识符', () => {
|
||||
expect(viewDataSchema.safeParse({ ...spec, case: { ...spec.case, ledger: 'verdict' } }).success).toBe(false);
|
||||
expect(viewDataSchema.safeParse({ ...spec, case: { ...spec.case, key: 'Eval-Ref' } }).success).toBe(false);
|
||||
expect(viewDataSchema.safeParse({ ...spec, rows: [{ as: 'x', ledger: 'a.b', by: { column: 'c d', from: 'case' } }] }).success).toBe(false);
|
||||
});
|
||||
it('模版不带 data 照旧;带了就按 schema 归型', () => {
|
||||
expect(viewTemplateSchema.parse({ name: 'p08', html: '<p/>' })).toEqual({ name: 'p08', html: '<p/>' });
|
||||
expect(viewTemplateSchema.parse({ name: 'board', html: '<p/>', data: spec }).data).toEqual(spec);
|
||||
});
|
||||
});
|
||||
@@ -307,6 +307,8 @@ export const workDoneSchema = z.object({
|
||||
by: staffIdSchema,
|
||||
result: workOrderResultSchema,
|
||||
values: z.record(z.string(), z.string()).optional(),
|
||||
/** 人工验收清单里被点成「达标」的那几条(留痕:谁核了什么) */
|
||||
checked: z.array(z.string()).optional(),
|
||||
});
|
||||
export type WorkDone = z.infer<typeof workDoneSchema>;
|
||||
|
||||
@@ -429,6 +431,14 @@ export const acceptanceWorkOrderSchema = z.object({
|
||||
mode: z.literal('acceptance'),
|
||||
deliverable: deliverableIdSchema,
|
||||
criteria: z.array(acceptanceCriterionCheckSchema),
|
||||
/**
|
||||
* **人工验收清单**(2026-08-27 用户定:「验收卡的条目不是系统判断的条目,是人要对这个验收工单的内容
|
||||
* 进行审核需要的步骤」)—— 人核这份工单要走的步骤,一条一句可核对的动作,**每份验收工单固定**
|
||||
* (剧本 `acceptanceChecklist` 声明,引擎发卡时带上)。卡上一条一枚「达标 / 不达标」开关,
|
||||
* 全部点成达标才能按「确认通过」;服务端同样把关(`checked` ⊇ `checklist`)。
|
||||
* 系统判的判据(`criteria`)仍在 —— 它们把提交门,不再摆在卡上让人签。
|
||||
*/
|
||||
checklist: z.array(z.string()).optional(),
|
||||
actions: z.array(actionIntentViewSchema).optional(),
|
||||
/** 报告可修订:字段随成果 schema 渲染,AI 拟值已填 */
|
||||
fields: z.array(formFieldSchema).default([]),
|
||||
@@ -527,6 +537,8 @@ export const decisionCardSchema = z.object({
|
||||
* (工单 · 验收 / 通过 · 驳回),机制仍是决策卡。由剧本 `cardVariant` 声明、引擎决定,不靠模型抄。
|
||||
*/
|
||||
variant: z.enum(['review']).optional(),
|
||||
/** 人工验收清单(与 `acceptanceWorkOrderSchema.checklist` 同一件事)—— 审核卡穿验收工单的衣服,清单也一样带 */
|
||||
checklist: z.array(z.string()).optional(),
|
||||
ask: z.string(),
|
||||
basis: basisSchema,
|
||||
recommend: z.string().optional(),
|
||||
@@ -547,6 +559,8 @@ export const decisionCardSchema = z.object({
|
||||
at: z.iso.datetime(),
|
||||
by: staffIdSchema,
|
||||
note: z.string().optional(),
|
||||
/** 人工验收清单里被点成「达标」的那几条 */
|
||||
checked: z.array(z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
@@ -759,6 +773,8 @@ export const workOrderReplySchema = z.discriminatedUnion('mode', [
|
||||
comment: z.string().optional(),
|
||||
/** 选择组的出口选择:恰发一口,随放行一并确认(AM 1.2) */
|
||||
selectedPorts: z.record(z.string(), z.string()).default({}),
|
||||
/** 人工验收清单里点成「达标」的条目 —— 通过时必须把清单点全(服务端把关) */
|
||||
checked: z.array(z.string()).optional(),
|
||||
}),
|
||||
]);
|
||||
export type WorkOrderReply = z.infer<typeof workOrderReplySchema>;
|
||||
@@ -769,6 +785,8 @@ export const chatCardReplySchema = z.discriminatedUnion('k', [
|
||||
cardId: cardIdSchema,
|
||||
value: z.string(),
|
||||
note: z.string().optional(),
|
||||
/** 审核卡的人工验收清单里点成「达标」的条目 —— 选「通过」时必须点全 */
|
||||
checked: z.array(z.string()).optional(),
|
||||
}),
|
||||
z.object({
|
||||
k: z.literal('elicitation'),
|
||||
|
||||
@@ -168,6 +168,13 @@ export const planStepSchema = z.object({
|
||||
* 归活动声明、不归模型临场发挥;模型写不写都一样。
|
||||
*/
|
||||
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<typeof planStepSchema>;
|
||||
|
||||
@@ -229,11 +236,52 @@ export const skillSchema = z.object({
|
||||
});
|
||||
export type Skill = z.infer<typeof skillSchema>;
|
||||
|
||||
/** `域.对象` 两段地址(与 `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`(案号本身)或 `<as>.<列>`(前面某个集合那一列的值) */
|
||||
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` 排序。作用域里 `<as>` = 行数组、`<as>.<列>` = 首行标量、
|
||||
* `<as 去符号>_rows` = 行数、`<as 去符号>_<列>_<值>` = 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<typeof viewDataRowsSchema>;
|
||||
export const viewDataSchema = z.object({
|
||||
case: z.object({ ledger: viewLedgerAddress, key: viewColumn, prefix: z.string() }),
|
||||
rows: z.array(viewDataRowsSchema),
|
||||
});
|
||||
export type ViewData = z.infer<typeof viewDataSchema>;
|
||||
/** `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<typeof viewTemplateSchema>;
|
||||
|
||||
export const manifestAssetsSchema = z.object({
|
||||
businessModel: businessModelSchema,
|
||||
skills: z.array(skillSchema).default([]),
|
||||
/** 视图 HTML 模版 —— 模版是内容不是代码,容器净化渲染、无脚本执行权 */
|
||||
viewTemplates: z.array(z.object({ name: z.string().min(1), html: z.string() })).default([]),
|
||||
viewTemplates: z.array(viewTemplateSchema).default([]),
|
||||
/** 卡内容 schema */
|
||||
cardSchemas: z.record(z.string(), jsonSchemaSchema).default({}),
|
||||
/** 蒸馏钩子:终态封卷 / 里程碑达成时从任务记忆归纳 */
|
||||
@@ -312,5 +360,12 @@ 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<typeof installVerdictSchema>;
|
||||
|
||||
Reference in New Issue
Block a user