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:
xcs
2026-08-27 23:20:16 +08:00
co-authored by Claude Fable 5
parent 909b1fe4b7
commit 7b85a79323
79 changed files with 3903 additions and 1297 deletions
@@ -0,0 +1,132 @@
/**
* 数据工单确认即入库(2026-08-27 用户定)—— 这段错了不报错:人按下确认、卡记成办结,
* 而库里一行没有;要到整单放行才发现,或者永远发现不了(试跑单不落主数据)。
*
* 这里测的是编排(纯逻辑 + 假台账):落哪几张、落成什么样留什么痕;真库那一侧在
* `ledger-store.it.test.ts`(范围放行三条)。
*/
import { describe, expect, it } from 'vitest';
import type { TaskId, WorkOrder } from '@contracts';
import { workOrderSchema } from '@contracts';
import type { ReleaseScope, ReleaseWriteResult } from '@modules/ledger';
import { dataOrderLedgers, landDataOrder } from '../data-order-landing.ts';
const T = 'task-1' as TaskId;
/** 夹具走契约 schema 造 —— 造出来的就是一张合法工单,不靠 as 硬转 */
const order = (shape: Record<string, unknown>): WorkOrder =>
workOrderSchema.parse({
id: 'wo-1',
at: '2026-08-27T10:00:00Z',
title: '数据工单 · 芯片主数据创建(P-08):LM2596S-3.3',
basis: [{ k: '触发原因', v: '查无记录' }],
ask: '请建齐',
fields: [],
...shape,
});
const dataOrder = (extra: Record<string, unknown> = {}): WorkOrder =>
order({ mode: 'execution', variant: 'data', gateLedgers: ['parts.profile', 'parts.parameter'], ...extra });
describe('dataOrderLedgers —— 确认之后该入库的表', () => {
it('数据工单:与提交闸同源 —— 绑了 blocks 看绑的,否则看 gateLedgers;去重、去脏', () => {
expect(dataOrderLedgers(dataOrder())).toEqual(['parts.profile', 'parts.parameter']);
expect(dataOrderLedgers(dataOrder({ gateLedgers: [' parts.profile ', 'parts.profile', 'bad address', ''] }))).toEqual(['parts.profile']);
const bound = dataOrder({ blocks: [{ t: 'table', title: '表', cols: ['a'], rows: [], ledger: 'parts.document' }] });
expect(dataOrderLedgers(bound)).toEqual(['parts.document']);
});
it('普通执行工单 / 验收工单 / 没说自己建哪张表的数据工单 → 空(不替它猜,更不回落到必交付物)', () => {
expect(dataOrderLedgers(order({ mode: 'execution', gateLedgers: ['parts.profile'] }))).toEqual([]);
expect(dataOrderLedgers(dataOrder({ gateLedgers: undefined }))).toEqual([]);
expect(dataOrderLedgers(order({ mode: 'acceptance', criteria: [], actions: [], deliverable: 'x', gateLedgers: ['parts.profile'] }))).toEqual([]);
});
});
function harness(reply: ReleaseWriteResult | Error) {
const calls: Array<{ taskId: TaskId; scope: ReleaseScope | undefined }> = [];
const notes: Array<{ kind: string; title: string; detail: Record<string, unknown> }> = [];
let touched = 0;
const deps = {
ledger: {
releaseWrite: async (taskId: TaskId, scope?: ReleaseScope) => {
calls.push({ taskId, scope });
if (reply instanceof Error) throw reply;
return reply;
},
},
memory: {
append: async (entry: { kind: string; title: string; detail: Record<string, unknown> }) => {
notes.push({ kind: entry.kind, title: entry.title, detail: entry.detail });
},
},
labelOf: async (address: string) => ({ 'parts.profile': '芯片主数据', 'parts.parameter': '标准化参数集' })[address] ?? address,
touched: () => {
touched += 1;
},
};
return { deps, calls, notes, touched: () => touched };
}
const merged = (rows: Array<[string, string]>): ReleaseWriteResult => ({
mergedRows: rows.length,
emittedEvents: rows.length,
landedEarlierRows: 0,
simulatedRows: 0,
merged: rows.map(([object, pn]) => ({ domain: 'parts', object, key: { internal_pn: pn }, values: {} })),
});
describe('landDataOrder —— 确认落下之后', () => {
it('只并这张卡的那几张表(范围放行),归因带卡号;痕点名哪几张表各几行,并给模型指路;数据域跟着刷', async () => {
const h = harness(merged([['profile', 'A'], ['parameter', 'A'], ['parameter', 'A']]));
const got = await landDataOrder(h.deps, T, dataOrder());
expect(got.landed).toBe(true);
expect(h.calls).toEqual([
{ taskId: T, scope: { only: [{ domain: 'parts', object: 'profile' }, { domain: 'parts', object: 'parameter' }], cause: 'data-order:wo-1' } },
]);
expect(h.notes).toHaveLength(1);
expect(h.notes[0]?.kind).toBe('ledger-write');
expect(h.notes[0]?.title).toContain('芯片主数据 1 行、标准化参数集 2 行已进正式表');
expect(h.notes[0]?.title).toContain('别再往这几张表写它们');
expect(h.notes[0]?.detail).toMatchObject({
workOrderId: 'wo-1',
rows: 3,
tables: [
{ address: 'parts.profile', rows: 1 },
{ address: 'parts.parameter', rows: 2 },
],
});
expect(h.touched()).toBe(1);
});
it('试跑单挡下的主数据:痕如实说「只留在活动缓存」,数据域不刷', async () => {
const h = harness({ mergedRows: 0, emittedEvents: 0, landedEarlierRows: 0, simulatedRows: 3, merged: [] });
await landDataOrder(h.deps, T, dataOrder());
expect(h.notes[0]?.title).toContain('试跑');
expect(h.notes[0]?.title).toContain('未落正式表');
expect(h.touched()).toBe(0);
});
it('同一张卡确认两次:第二次无事发生,痕也这么说', async () => {
const h = harness({ mergedRows: 0, emittedEvents: 0, landedEarlierRows: 3, simulatedRows: 0, merged: [] });
await landDataOrder(h.deps, T, dataOrder());
expect(h.notes[0]?.title).toContain('早已入库');
});
it('不是数据工单就一个字不动(不调台账、不留痕)', async () => {
const h = harness(merged([]));
const got = await landDataOrder(h.deps, T, order({ mode: 'execution' }));
expect(got).toEqual({ landed: false, reason: 'not-data-order' });
expect(h.calls).toEqual([]);
expect(h.notes).toEqual([]);
});
it('入库失败不抛:人已确认的事实不倒退;原话留痕、行仍在草稿层等放行', async () => {
const h = harness(new Error('草稿与正式版并发冲突:1 处字段已被他单改动'));
const got = await landDataOrder(h.deps, T, dataOrder());
expect(got).toMatchObject({ landed: false, reason: 'failed' });
expect(h.notes[0]?.title).toContain('没能入库');
expect(h.notes[0]?.title).toContain('并发冲突');
expect(h.notes[0]?.title).toContain('放行时再并');
});
});
@@ -565,6 +565,14 @@ describe('createOnly:键已在正式表 → 拒并指路', () => {
expect(why).toContain('drop');
});
it('本单经数据工单确认已入库的那一行 → 拒回换一句话:建好了别再写,不点名 drop(撤了读面少一行)', () => {
const why = createOnlyViolation(target, { internal_pn: 'LM2596S-ADJ' }, formal, [{ internal_pn: 'LM2596S-ADJ' }]);
expect(why).toContain('已入库');
expect(why).not.toContain('drop');
// 另一颗在库的料号与本单无关,照旧是原来那句
expect(createOnlyViolation(target, { internal_pn: 'LM2596S-5.0' }, formal, [{ internal_pn: 'LM2596S-ADJ' }])).toContain('drop');
});
it('库里没有的料号 → 放行;复合主键按全部键列比', () => {
expect(createOnlyViolation(target, { internal_pn: 'XL2596S-5.0' }, formal)).toBeUndefined();
const sub = { label: '标准化参数集', keys: ['internal_pn', 'param_name'] };