Archived
审核通过与否读基座数;「重做」不再被指向 finish;审核卡摆成验收工单(W05 1.4.7)
用户两报:①「我上一步明确要求重做了,怎么会继续向下推进」②「验收工单弹出的是需要你定,明显不对」。 ①病根两处合谋:人两次答 custom:重做一次,模型把一模一样的问法又发了一遍被闸拦下;而拒回的话 不分答案一律说「照这个选择把结论落进台账那面旗,再 finish」—— 对通过是对的,对重做是把模型往错路上推, 它照办写了 review_approved_total = 1,而 S4 的哨兵读的正是这面模型自写的旗(被叫的那一方自己记账,A6 那一课)。 - 基座新读数 decision_last / decision_<值>_total(按 card-reply 数);S4 哨兵改读 decision_last = "approve", 人最后一次真点了通过才开门;静态审认这几个名字。 - 拒回 / 观察 / 事实行三处同一段 chosenGuidance 按答案分岔:custom: 不是通过 —— 按他写的改、换问法重发、 不许写通过旗、不许 finish;选项值指回说明书那一档。 - S4 剧本驳回分支明写不许写旗、ask 开头必须写明这一轮改了什么(字面一样的卡会被吞)。 ②平台的验收工单是终局,中间审核只能靠决策卡循环 —— 但不该穿「需要你定」的衣服:契约 decisionCard.variant:'review', PlanStep.cardVariant 扩到 'review',cards.decide 带 itemId,引擎按剧本决定;对话区摆成「工单 · 验收 / 审核」。 2106 测 / typecheck / lint / depcruise 绿;api-report 已刷;CONTRACT.md 同步。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { emptyGate, findKnownRow, gateAddressesOf, gateOf, parseRowKey, resolveRowKey, rowChanged, writesOf } from '../routes/desk.ts';
|
||||
import { createOnlyViolation, missingReadings } from '../wiring/engines.ts';
|
||||
import { createOnlyViolation, decisionReadings, missingReadings } from '../wiring/engines.ts';
|
||||
|
||||
/** A1 受理记录的声明(裁到这一段用得着的几列):PN 与年用量必填 */
|
||||
const RECORD = {
|
||||
@@ -574,3 +574,19 @@ describe('createOnly:键已在正式表 → 拒并指路', () => {
|
||||
expect(createOnlyViolation(sub, { internal_pn: 'XL2596S-5.0', param_name: '输出电压' }, rows)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
/** 决策卡的回答是留痕里的事实(2026-08-27:审核哨兵不再读模型自写的旗) */
|
||||
describe('decisionReadings:按 card-reply 数', () => {
|
||||
it('最后一次选的值 + 按值计数;custom: 一律归 custom', () => {
|
||||
const trail = [
|
||||
{ kind: 'card-sent', detail: {} },
|
||||
{ kind: 'card-reply', detail: { value: 'custom:重做一次' } },
|
||||
{ kind: 'card-reply', detail: { value: 'custom:再改一下' } },
|
||||
{ kind: 'card-reply', detail: { value: 'approve' } },
|
||||
];
|
||||
expect(decisionReadings(trail)).toEqual({ decision_last: 'approve', decision_approve_total: 1, decision_reject_total: 0, decision_custom_total: 2 });
|
||||
});
|
||||
it('一张都没答 → last 为空串、各计数铺 0(哨兵里 decision_last = "approve" 恒假,门关着)', () => {
|
||||
expect(decisionReadings([])).toEqual({ decision_last: '', decision_approve_total: 0, decision_reject_total: 0, decision_custom_total: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -443,6 +443,31 @@ export function touchedRows(
|
||||
* 声明过的工具**一律先铺 0** —— 与 `_rows` 同一个理由:未绑定的名字参与比较求值为假,
|
||||
* 「没调过」与「调了 0 次」在业务上是同一件事,读数上也得是。
|
||||
*/
|
||||
/**
|
||||
* **决策卡的回答是留痕里的事实,不是模型的记账**(2026-08-27 W05 实测逼出):
|
||||
* 审核那一步的入场哨兵读的是模型自写的 `review_approved_total`,人答了「重做」它照写 1,一路推到放行。
|
||||
* 与 A6「该不该叫人不能由被叫的那一方说了算」同一课。基座按 card-reply 数:
|
||||
* `decision_last` —— 这一单最后一张决策卡人选的值(原样;`custom:…` 保留前缀)
|
||||
* `decision_<值>_total` —— 按值计数(`custom:` 开头的一律归 `decision_custom_total`)
|
||||
* 值的语义(approve 是通过)归活动的哨兵去写;这里只把事实摆出来。
|
||||
*/
|
||||
export function decisionReadings(
|
||||
trail: ReadonlyArray<{ readonly kind: string; readonly detail?: Record<string, unknown> | undefined }>,
|
||||
): Record<string, number | string> {
|
||||
const out: Record<string, number | string> = { decision_last: '', decision_approve_total: 0, decision_reject_total: 0, decision_custom_total: 0 };
|
||||
for (const entry of trail) {
|
||||
if (entry.kind !== 'card-reply') continue;
|
||||
const raw = entry.detail?.['value'];
|
||||
if (typeof raw !== 'string' || raw.trim() === '') continue;
|
||||
const value = raw.trim();
|
||||
out['decision_last'] = value;
|
||||
const token = value.startsWith('custom:') ? 'custom' : value.replace(/[^0-9a-zA-Z]+/g, '_');
|
||||
const key = `decision_${token}_total`;
|
||||
out[key] = Number(out[key] ?? 0) + 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function toolCallReadings(
|
||||
trail: ReadonlyArray<{ readonly kind: string; readonly title: string }>,
|
||||
declared: ReadonlyArray<{ readonly name: string }>,
|
||||
@@ -935,6 +960,7 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
||||
...tallyReadings(mine, declared),
|
||||
...distinctReadings(mine, declared),
|
||||
...toolCallReadings(trail, manifest.declarations.tools),
|
||||
...decisionReadings(trail),
|
||||
...mine,
|
||||
...touched,
|
||||
};
|
||||
@@ -1325,6 +1351,7 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
||||
}>;
|
||||
basis?: ReadonlyArray<{ k: string; v: string }>;
|
||||
allowCustom?: boolean;
|
||||
itemId?: string | undefined;
|
||||
}) {
|
||||
const cardSpan = deps.trace.beginAction({
|
||||
taskId: input.taskId,
|
||||
@@ -1333,10 +1360,17 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
||||
input: { options: input.options.map((o) => o.value) },
|
||||
});
|
||||
const recommended = input.options.find((o) => o.recommended === true);
|
||||
/* 审核卡(剧本 cardVariant:'review'):按验收工单的样子摆,机制仍是决策卡 —— 剧本说了算,不靠模型抄 */
|
||||
const reviewFace =
|
||||
input.itemId !== undefined &&
|
||||
(await deps.registry.manifestOf((await taskContext.of(input.taskId)).activityId)).assets.businessModel.steps?.find(
|
||||
(st) => st.item === input.itemId,
|
||||
)?.cardVariant === 'review';
|
||||
const issued = await deps.humanTool.issueChatCard(
|
||||
{ taskId: input.taskId, ownerId: input.ownerId, humanCallCap: input.humanCallCap },
|
||||
{
|
||||
k: 'decision',
|
||||
...(reviewFace ? { variant: 'review' as const } : {}),
|
||||
id: cardIdSchema.parse(ids.next()),
|
||||
at: clock.now().toISOString(),
|
||||
ask: input.ask,
|
||||
@@ -1744,7 +1778,8 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
||||
: (await deps.registry.manifestOf((await taskContext.of(input.taskId)).activityId)).assets.businessModel.steps?.find(
|
||||
(s) => s.item === input.itemId,
|
||||
)?.cardVariant;
|
||||
const variant = stepVariant ?? (input.card['variant'] === 'data' ? ('data' as const) : undefined);
|
||||
// 执行工单这一路只认 data(review 是决策卡那一路的面孔)
|
||||
const variant = stepVariant === 'data' || input.card['variant'] === 'data' ? ('data' as const) : undefined;
|
||||
const issued = await deps.humanTool.issueExecution(context, {
|
||||
title,
|
||||
basis: basis.length > 0 ? basis : [{ k: '来源', v: '案例模型当下局面' }],
|
||||
|
||||
Reference in New Issue
Block a user