Archived
feat(w05+platform): 确认闸条件档 gateWhen —— 类型不一致不许确认;量高改可缩;卡间互换动画
2026-08-31 用户三连: ① 「提示两颗料不是同一个芯片类型…就不应该能写入并继续下一步」—— 缺格闸只数必填空不空,管不了跨行一致性。平台加**确认闸条件档**:配方 card 声明 gateWhen(S-FEEL,只读基座读数)+ gateWhenSay,受理回传前在 submitGate 现算 (rows/missing/tally/distinct 全套读数按声明表现读),为假 400、拒回用发卡人那句话; 装包静态审与动作级 when 同一套审;评估不出的条件不成墙(坏表达式装包就拦)。 W05 钉 parts_profile_distinct_chip_type_id <= 1 —— 钉 profile 本表(草稿现读零滞后; 钉 sub_eval.part 那份要等配方 tick,人存完就点确认会被误拦),profile.chip_type_id 加 distinct 声明。e2e 案例 2 的空手探针从此吃的就是这道闸。 ② 「页面底部没东西还能向下滑一截」—— ViewContainer 量高从 max(documentElement.scrollHeight, body) 改 body 优先:documentElement 以 iframe 自身 高度为下限,内容缩回后它缩不回去,量高**只涨不缩**(参数编辑态把卡背拉长过一次, 底部从此留一截空白滚动)。body(margin 0)才是内容自己的高。 ③ 「两张卡片中间做出可替换的动画」—— 单点单向信号换成**对向双信号点**(左右互穿, 走在卡后、只在缝里现身)+ 中央 ⇄ 徽轻呼吸 —— 这界面是判「可否替换」的,动画说的 就是这件事。reduced-motion 全静。 测:契约 gateWhen 解析 + runner 透传(变异红)+ conditionGateOf 3 例(含坏表达式不成墙) + steps/activity 锚;2531 全仓四门绿;2.12.0 装库核过(gateWhen 入库/swapfx); :8787 已重建重启。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -689,3 +689,20 @@ describe('decisionReadings:按 card-reply 数', () => {
|
||||
expect(decisionReadings([])).toEqual({ decision_last: '', decision_approve_total: 0, decision_reject_total: 0, decision_custom_total: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('conditionGateOf:确认闸的条件档(2026-08-31,「类型不一致还能确认」逼出)', () => {
|
||||
it('条件为假 → 不受理,拒回用发卡人那句话;为真 / 没声明 → 不拦', async () => {
|
||||
const { conditionGateOf } = await import('../routes/desk.ts');
|
||||
const readings = { parts_profile_distinct_chip_type_id: 2 };
|
||||
const blocked = conditionGateOf('parts_profile_distinct_chip_type_id <= 1', '两颗料不是同一个芯片类型 —— 先改对再确认。', readings);
|
||||
expect(blocked?.ready).toBe(false);
|
||||
expect(blocked?.why).toContain('不是同一个芯片类型');
|
||||
expect(conditionGateOf('parts_profile_distinct_chip_type_id <= 1', '话', { parts_profile_distinct_chip_type_id: 1 })).toBeUndefined();
|
||||
expect(conditionGateOf(undefined, undefined, readings)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('表达式炸了不成墙(装包静态审管坏表达式;运行时评估不出的条件不该锁死一单)', async () => {
|
||||
const { conditionGateOf } = await import('../routes/desk.ts');
|
||||
expect(conditionGateOf('((((', '话', {})).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,7 +45,8 @@ function rowsOf<T>(result: unknown): T[] {
|
||||
}
|
||||
import { staffOf } from '../http-env.ts';
|
||||
import type { AppEnv } from '../http-env.ts';
|
||||
import { coerceToColumn, missingReadings, rowLabel } from '../wiring/engines.ts';
|
||||
import { evaluateCondition } from '@sfeel';
|
||||
import { coerceToColumn, distinctReadings, missingReadings, rowLabel, rowReadings, tallyReadings } from '../wiring/engines.ts';
|
||||
import { orderChat } from '../chat-order.ts';
|
||||
|
||||
|
||||
@@ -816,7 +817,41 @@ async function submitGate(
|
||||
for (const draft of await container.ledger.taskDrafts(taskId)) {
|
||||
(mine[`mine:${draft.domain}.${draft.object}`] ??= []).push({ ...draft.key, ...draft.patch });
|
||||
}
|
||||
return emptyGate(mine, watched, declared) ?? gateOf(missingReadings(mine, watched), watched);
|
||||
const base = emptyGate(mine, watched, declared) ?? gateOf(missingReadings(mine, watched), watched);
|
||||
if (!base.ready) return base;
|
||||
/*
|
||||
* **条件档**(2026-08-31,W05「两颗料不是同一个芯片类型还能确认」逼出):缺格闸都过了,
|
||||
* 发卡的人还声明了一条 S-FEEL 条件的,再按**全部声明表的基座读数**现算一遍 —— 为假就
|
||||
* 不受理。读数从本单草稿现读(rows/missing/tally/distinct 同一套函数),没有 tick 滞后:
|
||||
* 人刚在件上改完类型、点确认,算的就是改完之后的样子。
|
||||
*/
|
||||
return (
|
||||
conditionGateOf(order.gateWhen, order.gateWhenSay, {
|
||||
...rowReadings(mine, manifest.declarations.ledger),
|
||||
...missingReadings(mine, manifest.declarations.ledger),
|
||||
...tallyReadings(mine, manifest.declarations.ledger),
|
||||
...distinctReadings(mine, manifest.declarations.ledger),
|
||||
}) ?? base
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认闸的条件档 —— S-FEEL 为假就交不了,拒回用发卡人写的那句话(display 与服务端同一段)。
|
||||
* 条件只读基座读数(机检纪律)。表达式炸了按「不拦」:坏表达式装包时静态审就该拦
|
||||
* (checkWhen 已审),运行时评估不出的条件不该成墙(「没有依据的时候不拦人」)。
|
||||
*/
|
||||
export function conditionGateOf(
|
||||
when: string | undefined,
|
||||
say: string | undefined,
|
||||
readings: Readonly<Record<string, string | number>>,
|
||||
): { ready: boolean; why: string; blanks: string[] } | undefined {
|
||||
if (when === undefined || when.trim() === '') return undefined;
|
||||
try {
|
||||
if (evaluateCondition(when, readings)) return undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
return { ready: false, why: say ?? '这张卡声明的确认条件还不成立 —— 回件上把它办对再来确认。', blanks: [] };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -612,6 +612,8 @@ export function autoRecipeFindings(manifest: ActivityManifest): StaticAuditFindi
|
||||
const where = `${at}.do[${index}]`;
|
||||
/* 动作级 `when`:语法 + 名字都审(它看得见的只有 scope 与 let,没有行内列) */
|
||||
if (action.when !== undefined) checkWhen(action.when, where, letNames);
|
||||
/* 确认闸的条件档与动作级 when 同一作用域(基座读数),同一套审 */
|
||||
if (action.kind === 'card' && action.gateWhen !== undefined) checkWhen(action.gateWhen, where, letNames);
|
||||
if (action.kind === 'project') {
|
||||
checkAddress(action.into, where);
|
||||
walkSource(action.from, where);
|
||||
|
||||
@@ -183,6 +183,10 @@
|
||||
(2026-08-30 e2e 案例 2 的死锁,当时以「W05 ②③ 两笔不带 prune」绕开,代价是**没落地的**
|
||||
陈旧行也不清 —— 2026-08-31 用户实测点名的「改回同一类型,下面还是两个类型」)。
|
||||
模型那一路不传:那句「已入库撤不掉」的拒回是它的行动指南,一个字不变。
|
||||
- 配方 `card` 多两个可选键 `gateWhen` / `gateWhenSay`(2026-08-31):确认闸的**条件档** ——
|
||||
除了「必填缺几格」,发卡的人还可声明一条 S-FEEL 条件(只读基座读数,装包静态审同
|
||||
`when` 一套审),受理回传前在 app 层 `submitGate` 现算,为假不受理、拒回用发卡人那句话
|
||||
(W05 第一例:两颗料不是同一个芯片类型不许确认)。运行时评估不出的条件不成墙。
|
||||
|
||||
同批三条不变量:
|
||||
- **配方由 `liveStep` 驱动,不由 `current` 的兜底驱动**:兜底(在场的第一个有剧本的项)不过滤
|
||||
|
||||
@@ -398,6 +398,8 @@ describe('card 与 submit', () => {
|
||||
standard: '必填格全部填上,红带消失',
|
||||
basis: [{ k: '候选料号', v: { in: 'pn' } }],
|
||||
gateLedgers: ['parts.profile', 'parts.parameter'],
|
||||
gateWhen: 'parts_profile_distinct_chip_type_id <= 1',
|
||||
gateWhenSay: '两颗料不是同一个芯片类型 —— 先改对再确认。',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -415,6 +417,9 @@ describe('card 与 submit', () => {
|
||||
standard: '必填格全部填上,红带消失',
|
||||
basis: [{ k: '候选料号', v: 'XL2596S-5.0' }],
|
||||
gateLedgers: ['parts.profile', 'parts.parameter'],
|
||||
/* 确认闸条件档随卡走(2026-08-31)—— 服务端 submitGate 靠它拦「类型不一致还确认」 */
|
||||
gateWhen: 'parts_profile_distinct_chip_type_id <= 1',
|
||||
gateWhenSay: '两颗料不是同一个芯片类型 —— 先改对再确认。',
|
||||
fields: [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -371,6 +371,9 @@ export function createAutoRunner(deps: AutoRunnerDeps): {
|
||||
...(action.guide === undefined ? {} : { steps: [...action.guide] }),
|
||||
...(action.standard === undefined ? {} : { standard: evalText(action.standard, base) }),
|
||||
...(action.gateLedgers === undefined ? {} : { gateLedgers: [...action.gateLedgers] }),
|
||||
...(action.gateWhen === undefined ? {} : { gateWhen: action.gateWhen }),
|
||||
|
||||
...(action.gateWhenSay === undefined ? {} : { gateWhenSay: action.gateWhenSay }),
|
||||
},
|
||||
});
|
||||
return { wrote: 0, dropped: 0, cardId: id, stop: true };
|
||||
|
||||
Reference in New Issue
Block a user