A
This commit is contained in:
@@ -160,57 +160,6 @@ function normalizeOpName(k) {
|
|||||||
return OP_MAP[String(k).toLowerCase().trim()] || null;
|
return OP_MAP[String(k).toLowerCase().trim()] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseBumpSpec(payload) {
|
|
||||||
try {
|
|
||||||
if (payload && typeof payload === 'object' && payload.kind && Number.isFinite(Number(payload.value))) {
|
|
||||||
return { kind: String(payload.kind), value: Number(payload.value), raw: payload.raw };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof payload === 'number' && Number.isFinite(payload)) {
|
|
||||||
return { kind: 'delta', value: payload, raw: payload };
|
|
||||||
}
|
|
||||||
|
|
||||||
const raw = String(payload ?? '').trim();
|
|
||||||
if (!raw) return null;
|
|
||||||
|
|
||||||
const num = Number(raw);
|
|
||||||
if (Number.isFinite(num) && !/[*/^%]/.test(raw)) {
|
|
||||||
return { kind: 'delta', value: num, raw };
|
|
||||||
}
|
|
||||||
|
|
||||||
const mPct = raw.match(/^([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*%$/);
|
|
||||||
if (mPct) return { kind: 'percent', value: Number(mPct[1]), raw };
|
|
||||||
|
|
||||||
const mOp = raw.match(/^([*/^])\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)$/);
|
|
||||||
if (mOp) {
|
|
||||||
const op = mOp[1] === '*' ? 'mul' : (mOp[1] === '/' ? 'div' : 'pow');
|
|
||||||
return { kind: op, value: Number(mOp[2]), raw };
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeBumpResult(payload, currentValue) {
|
|
||||||
const spec = parseBumpSpec(payload);
|
|
||||||
if (!spec) return { ok: false, reason: 'delta-nan' };
|
|
||||||
|
|
||||||
const baseRaw = Number(currentValue);
|
|
||||||
const base = Number.isFinite(baseRaw) ? baseRaw : 0;
|
|
||||||
|
|
||||||
let next;
|
|
||||||
if (spec.kind === 'delta') next = base + spec.value;
|
|
||||||
else if (spec.kind === 'percent') next = base + (base * (spec.value / 100));
|
|
||||||
else if (spec.kind === 'mul') next = base * spec.value;
|
|
||||||
else if (spec.kind === 'div') {
|
|
||||||
if (!Number.isFinite(spec.value) || spec.value === 0) return { ok: false, reason: 'div-zero', spec };
|
|
||||||
next = base / spec.value;
|
|
||||||
} else if (spec.kind === 'pow') next = Math.pow(base, spec.value);
|
|
||||||
else return { ok: false, reason: 'delta-nan', spec };
|
|
||||||
|
|
||||||
if (!Number.isFinite(next)) return { ok: false, reason: 'result-nan', spec };
|
|
||||||
return { ok: true, base, next, delta: next - base, spec };
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============= 应用签名追踪 ============= */
|
/* ============= 应用签名追踪 ============= */
|
||||||
|
|
||||||
function getAppliedMap() {
|
function getAppliedMap() {
|
||||||
@@ -354,12 +303,10 @@ function parseBlock(innerText) {
|
|||||||
Array.isArray(value) ? arr.push(...value) : arr.push(value);
|
Array.isArray(value) ? arr.push(...value) : arr.push(value);
|
||||||
};
|
};
|
||||||
const putBump = (top, path, delta) => {
|
const putBump = (top, path, delta) => {
|
||||||
const spec = parseBumpSpec(delta);
|
const n = Number(String(delta).replace(/^\+/, ''));
|
||||||
if (!spec) return;
|
if (!Number.isFinite(n)) return;
|
||||||
ops.bump[top] ||= {};
|
ops.bump[top] ||= {};
|
||||||
const list = (ops.bump[top][path] ||= []);
|
ops.bump[top][path] = (ops.bump[top][path] ?? 0) + n;
|
||||||
if (spec.kind === 'delta') list.push(spec.value);
|
|
||||||
else list.push(spec);
|
|
||||||
};
|
};
|
||||||
const putDel = (top, path) => {
|
const putDel = (top, path) => {
|
||||||
ops.del[top] ||= [];
|
ops.del[top] ||= [];
|
||||||
@@ -790,7 +737,7 @@ function parseBlock(innerText) {
|
|||||||
const rel = rest.join('.');
|
const rel = rest.join('.');
|
||||||
if (curOp === 'set') putSet(top, rel, text);
|
if (curOp === 'set') putSet(top, rel, text);
|
||||||
else if (curOp === 'push') putPush(top, rel, text);
|
else if (curOp === 'push') putPush(top, rel, text);
|
||||||
else if (curOp === 'bump') putBump(top, rel, text);
|
else if (curOp === 'bump') putBump(top, rel, Number(text));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -826,7 +773,7 @@ function parseBlock(innerText) {
|
|||||||
for (const item of arr) putDel(top, rel ? `${rel}.${item}` : item);
|
for (const item of arr) putDel(top, rel ? `${rel}.${item}` : item);
|
||||||
}
|
}
|
||||||
else if (curOp === 'bump') {
|
else if (curOp === 'bump') {
|
||||||
for (const item of arr) putBump(top, rel, item);
|
for (const item of arr) putBump(top, rel, Number(item));
|
||||||
}
|
}
|
||||||
stack.pop();
|
stack.pop();
|
||||||
handledList = true;
|
handledList = true;
|
||||||
@@ -865,7 +812,7 @@ function parseBlock(innerText) {
|
|||||||
putDel(top, target);
|
putDel(top, target);
|
||||||
}
|
}
|
||||||
} else if (curOp === 'bump') {
|
} else if (curOp === 'bump') {
|
||||||
putBump(top, rel, stripQ(rhs));
|
putBump(top, rel, Number(stripQ(rhs)));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -901,7 +848,7 @@ function parseBlock(innerText) {
|
|||||||
} else if (curOp === 'del') {
|
} else if (curOp === 'del') {
|
||||||
putDel(top, rel ? `${rel}.${val}` : val);
|
putDel(top, rel ? `${rel}.${val}` : val);
|
||||||
} else if (curOp === 'bump') {
|
} else if (curOp === 'bump') {
|
||||||
putBump(top, rel, val);
|
putBump(top, rel, Number(val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1098,7 +1045,7 @@ function getEffectiveParentNode(p) {
|
|||||||
/**
|
/**
|
||||||
* 守护验证
|
* 守护验证
|
||||||
*/
|
*/
|
||||||
export function guardValidate(op, absPath, payload, currentOverride) {
|
export function guardValidate(op, absPath, payload) {
|
||||||
if (guardianState.bypass) return { allow: true, value: payload };
|
if (guardianState.bypass) return { allow: true, value: payload };
|
||||||
|
|
||||||
const p = normalizePath(absPath);
|
const p = normalizePath(absPath);
|
||||||
@@ -1179,19 +1126,10 @@ export function guardValidate(op, absPath, payload, currentOverride) {
|
|||||||
|
|
||||||
// 增量操作
|
// 增量操作
|
||||||
if (op === 'bump') {
|
if (op === 'bump') {
|
||||||
const effectiveCurrent = (currentOverride !== undefined) ? currentOverride : currentValue;
|
let d = Number(payload);
|
||||||
const computed = computeBumpResult(payload, effectiveCurrent);
|
if (!Number.isFinite(d)) return { allow: false, reason: 'delta-nan' };
|
||||||
if (!computed?.ok) {
|
|
||||||
if (xbLog.isEnabled?.()) {
|
|
||||||
try { xbLog.warn(MODULE_ID, `bump payload rejected: path=${p} reason=${computed?.reason || 'delta-nan'}`); } catch {}
|
|
||||||
}
|
|
||||||
return { allow: false, reason: computed?.reason || 'delta-nan' };
|
|
||||||
}
|
|
||||||
|
|
||||||
let d = computed.delta;
|
if (currentValue === undefined) {
|
||||||
const base = computed.base;
|
|
||||||
|
|
||||||
if (effectiveCurrent === undefined) {
|
|
||||||
if (parentPath) {
|
if (parentPath) {
|
||||||
const lastSeg = p.split('.').pop() || '';
|
const lastSeg = p.split('.').pop() || '';
|
||||||
const isIndex = /^\d+$/.test(lastSeg);
|
const isIndex = /^\d+$/.test(lastSeg);
|
||||||
@@ -1214,16 +1152,16 @@ export function guardValidate(op, absPath, payload, currentOverride) {
|
|||||||
if (d < -step) d = -step;
|
if (d < -step) d = -step;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cur = Number(effectiveCurrent);
|
const cur = Number(currentValue);
|
||||||
if (!Number.isFinite(cur)) {
|
if (!Number.isFinite(cur)) {
|
||||||
const baseValue = 0 + d;
|
const base = 0 + d;
|
||||||
const cl = clampNumberWithConstraints(baseValue, node);
|
const cl = clampNumberWithConstraints(base, node);
|
||||||
if (!cl.ok) return { allow: false, reason: 'number-constraint' };
|
if (!cl.ok) return { allow: false, reason: 'number-constraint' };
|
||||||
setTypeLockIfUnknown(p, cl.value);
|
setTypeLockIfUnknown(p, base);
|
||||||
return { allow: true, value: cl.value };
|
return { allow: true, value: cl.value };
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = Number.isFinite(base) ? (base + d) : (cur + d);
|
const next = cur + d;
|
||||||
const clamped = clampNumberWithConstraints(next, node);
|
const clamped = clampNumberWithConstraints(next, node);
|
||||||
if (!clamped.ok) return { allow: false, reason: 'number-constraint' };
|
if (!clamped.ok) return { allow: false, reason: 'number-constraint' };
|
||||||
return { allow: true, value: clamped.value };
|
return { allow: true, value: clamped.value };
|
||||||
@@ -1391,7 +1329,11 @@ export function rulesLoadFromTree(valueTree, basePath) {
|
|||||||
String(t).trim().startsWith('$') ? String(t).trim() : ('$' + String(t).trim())
|
String(t).trim().startsWith('$') ? String(t).trim() : ('$' + String(t).trim())
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetPath = curAbs ? `${curAbs}.${targetToken}` : targetToken;
|
const baseNorm = normalizePath(curAbs || '');
|
||||||
|
const tokenNorm = normalizePath(targetToken);
|
||||||
|
const targetPath = (baseNorm && (tokenNorm === baseNorm || tokenNorm.startsWith(baseNorm + '.')))
|
||||||
|
? tokenNorm
|
||||||
|
: (curAbs ? `${curAbs}.${targetToken}` : targetToken);
|
||||||
const absPath = normalizePath(targetPath);
|
const absPath = normalizePath(targetPath);
|
||||||
const delta = parseDirectivesTokenList(dirs);
|
const delta = parseDirectivesTokenList(dirs);
|
||||||
|
|
||||||
@@ -2094,15 +2036,19 @@ async function applyVariablesForMessage(messageId) {
|
|||||||
// BUMP 操作
|
// BUMP 操作
|
||||||
else if (op.operation === 'bump') {
|
else if (op.operation === 'bump') {
|
||||||
for (const [k, delta] of Object.entries(op.data)) {
|
for (const [k, delta] of Object.entries(op.data)) {
|
||||||
|
const num = Number(delta);
|
||||||
|
if (!Number.isFinite(num)) continue;
|
||||||
|
|
||||||
const localPath = joinPath(subPath, k);
|
const localPath = joinPath(subPath, k);
|
||||||
const absPath = localPath ? `${root}.${localPath}` : root;
|
const absPath = localPath ? `${root}.${localPath}` : root;
|
||||||
const stdPath = normalizePath(absPath);
|
const stdPath = normalizePath(absPath);
|
||||||
const deltaList = Array.isArray(delta) ? delta : [delta];
|
|
||||||
|
|
||||||
for (const entry of deltaList) {
|
let allow = true;
|
||||||
const spec = parseBumpSpec(entry);
|
let useDelta = num;
|
||||||
if (!spec) continue;
|
|
||||||
|
|
||||||
|
const res = guardValidate('bump', stdPath, num);
|
||||||
|
allow = !!res?.allow;
|
||||||
|
if (allow && 'value' in res && Number.isFinite(res.value)) {
|
||||||
let curr;
|
let curr;
|
||||||
try {
|
try {
|
||||||
const pth = norm(localPath || '');
|
const pth = norm(localPath || '');
|
||||||
@@ -2116,39 +2062,17 @@ async function applyVariablesForMessage(messageId) {
|
|||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
const res = guardValidate('bump', stdPath, spec, curr);
|
|
||||||
const allow = !!res?.allow;
|
|
||||||
if (!allow) {
|
|
||||||
guardDenied++;
|
|
||||||
if (debugOn && guardDeniedSamples.length < 8) guardDeniedSamples.push({ op: 'bump', path: stdPath });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseNum = Number(curr);
|
const baseNum = Number(curr);
|
||||||
const targetNum = Number(res.value);
|
const targetNum = Number(res.value);
|
||||||
const useDelta = Number.isFinite(targetNum)
|
useDelta = (Number.isFinite(targetNum) ? targetNum : num) - (Number.isFinite(baseNum) ? baseNum : 0);
|
||||||
? (targetNum - (Number.isFinite(baseNum) ? baseNum : 0))
|
|
||||||
: (spec.kind === 'delta' ? spec.value : NaN);
|
|
||||||
|
|
||||||
if (!Number.isFinite(useDelta)) {
|
|
||||||
guardDenied++;
|
|
||||||
if (debugOn && guardDeniedSamples.length < 8) guardDeniedSamples.push({ op: 'bump', path: stdPath });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debugOn && spec.kind !== 'delta') {
|
|
||||||
try {
|
|
||||||
const baseVal = Number.isFinite(baseNum) ? baseNum : 0;
|
|
||||||
const nextVal = Number.isFinite(targetNum) ? targetNum : (baseVal + useDelta);
|
|
||||||
const expr = (spec.raw !== undefined && spec.raw !== null && String(spec.raw).trim() !== '')
|
|
||||||
? String(spec.raw)
|
|
||||||
: `${spec.kind}:${spec.value}`;
|
|
||||||
xbLog.info(MODULE_ID, `plot-log bump-math path=${stdPath} base=${baseVal} expr=${expr} next=${nextVal}`);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
bumpAtPath(rec, norm(localPath || ''), useDelta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!allow) {
|
||||||
|
guardDenied++;
|
||||||
|
if (debugOn && guardDeniedSamples.length < 8) guardDeniedSamples.push({ op: 'bump', path: stdPath });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bumpAtPath(rec, norm(localPath || ''), useDelta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2462,4 +2386,4 @@ export {
|
|||||||
rulesSetTable,
|
rulesSetTable,
|
||||||
rulesLoadFromMeta,
|
rulesLoadFromMeta,
|
||||||
rulesSaveToMeta,
|
rulesSaveToMeta,
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user