Repository navigation
fix(ui-core): evaluatePlainCondition reads a bare-string predicate as CEL, as evaluateCondition does - #27773
Conversation
… CEL
A bare string handed to the published evaluatePlainCondition export now
takes the CEL route evaluateCondition gives it, as the spec defines the
shorthand; a ${...} template stays on the legacy engine. Its JS-grammar
pins are rewritten to the CEL spelling, with a JS-only spelling pinned as
refused (false) and a parity pin against evaluateCondition.
Claude-Session: https://claude.ai/code/session_01Utp6CYKdX5M2WLtcLDH7qF
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CYKdX5M2WLtcLDH7qF Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh |
|
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh Contract reviewServed-tier: Inputs: card #27605 body and its 3 comments (triage 6500555416, dispatch 6508518080, dev report 6510355078), PR #27773 body, its 3-file list and the net diff against ① Derived judgmentsAccept-set of the published export
② Semver level
③ Boundary flagsDev report 6510355078 is on this head (
④ Design judgmentThe narrowing, held against the spec on main and the cited code; tried to break it on fault policy, the blank leg, the context bag, double evaluation and the template leg.
Findings (none must-fix):
Implemented-by: VERDICT: PASS Rendered 2026-10-29T22:59Z. Generated by Claude Code |
…n on its own facts Claude-Session: https://claude.ai/code/session_01Utp6CYKdX5M2WLtcLDH7qF Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh Regen-provenance: 6510497934 · The one commit after the reviewed head answers the record's only should-fix. It rewrites the reason text of the Generated by Claude Code |
|
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh 席位核对ACCEPT · head
Generated by Claude Code |
Requested by Bill · project thread
Fixes #27605
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no skill or doc names
evaluatePlainCondition(repo grep overskills/,content/,docs/: 0 hits); the CEL teaching for bare predicates is #27587's维护者速读
@object-ui/core对外导出的evaluatePlainCondition(condition, record)把「裸字符串」条件按 CEL 求值,与evaluateCondition走同一个 CEL 引擎;只有${…}模板仍走旧 JS 求值器。===、.includes()、.length等)现在返回 false,原来能算出 true;仓内除自身测试外零调用方(实测见下)。回滚即 revert 本 PR。ui/packages/core/src/evaluator/ExpressionEvaluator.ts(evaluatePlainCondition函数体)、ui/packages/core/src/evaluator/__tests__/ExpressionEvaluator.test.ts(describe('evaluatePlainCondition'))、.changeset/27605-plain-condition-cel.md。pnpm --dir ui exec vitest run packages/core/src/evaluator/__tests__/ExpressionEvaluator.test.ts,预期 88 个测试全绿。What changes
evaluatePlainCondition(@object-ui/core, published throughevaluator/index.ts): a bare string now goes toExpressionEvaluator.evaluateCondition(condition, { throwOnError: true }), which since fix(ui-core): read a bare-string predicate as CEL shorthand, as the spec and server do #27584 evaluates a bare string on the canonical@objectstack/formulaCEL engine (evaluateCelCondition). The evaluation context is unchanged ({ ...record, data: record }), so bothstatusanddata.statusresolve in CEL.throwOnError: trueturns a CEL fault into a throw, which the existingcatchanswers withfalse; a non-boolean result answersfalse(the engine seam already compares=== true). A blank condition answersfalseas it did before, by an explicit guard, becauseevaluateConditionanswers a blank gatetrue("no gate").${…}template is unchanged:evaluate(condition, { throwOnError: true }) === trueon the legacy engine..changeset/27605-plain-condition-cel.md:'@object-ui/core': minor, both declaration lines, a BREAKING note that teaches the CEL spelling with no FROM-TO list and no console-only escape hatch, ADR-0087 dispositionnot-required (no-migration-prescription)as in the precedent's changeset.Why
@objectstack/specdefines a bare string in a predicate slot as{ dialect: 'cel', source }(the string arm ofExpressionInputSchemainpackages/spec/src/shared/expression.zod.ts), the server reads it on CEL, and #27584 moved every console predicate path to CEL. This export was the one public reader left on the legacy JavaScript grammar (contract review of #27584, should-fix 3; triage graded it align, not retire).Tests
All in
ExpressionEvaluator.test.ts,describe('evaluatePlainCondition'):amount > 1000 && status == 'urgent'), true and false legs.status in [...],size(tags) > 0), each with a true and a false leg.falsewhere the JS grammar would have answeredtrue(===,.includes(), each beside its CEL control (==,.contains() answeringtrue, and no console line.data.amount > 1000as a bare CEL predicate.evaluatePlainConditionequalsnew ExpressionEvaluator({ ...record, data: record }).evaluateCondition(source).${…}template still reads===on the legacy engine.false.data.reference, invalid syntax, non-boolean result.Acceptance notes
Clause-②: no (narrowing)/Metadata-protocol: narrowedas the claim and the precedent (fix(ui-core): read a bare-string predicate as CEL shorthand, as the spec and server do #27584) do: a bare string that only the JS grammar reads now answersfalsefrom this export.d93d9be5(git grep evaluatePlainConditionover the whole tree,ui/andpackages/included): 0 callers outside its own file and its test file. Sibling repositories (HotCRM, cloud) were not measured: NOT MEASURED, out of this card's scope.false(unchanged), so it deliberately differs fromevaluateCondition's diagnosed "no gate"true; this export answers "does the condition hold", and a blank one never did. Pinned.throwOnErrorroute passeswarn: falseto the engine seam, so the answerfalseis the whole report. Pinned.Verification
Ablation (scripts/ablation-replace.mjs under the verify lock, restored to HEAD after)
return evaluator.evaluateCondition(condition, { throwOnError: true });replaced withreturn evaluator.evaluateExpression(condition) === true;(the legacy route): 3 failed of 88, the CEL-only, JS-only-refused and parity tests. Restore: blob equal to HEAD,git diff HEADempty.Local runs
pnpm --dir ui exec vitest run packages/core/: 218 files passed, 4141 tests passed, 27 skipped.pnpm --filter @object-ui/core type-check(tsc --noEmit && tsc -p tsconfig.test.json): exit 0.anys).node ui/scripts/check-new-cross-file-line-citations.mjs: 0 new citations.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 34 commands on this diff; each was run with its exit code recorded, and all 34 exit 0 on873fd637.--ranreconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN.pnpm --dir ui exec vitest run scripts/first exited 1 with 2 failures, both from this container's build state and not from the diff:unit-registry-absence-collisioncould not import 31 modules because@objectstack/clienthad nodist/here, andcheck-vi-mock-inherittimed out at 15 s on the shared box. Afterpnpm --filter '@objectstack/client...' build, the full command re-ran: 169 files passed, 2 skipped, 4902 tests passed, exit 0. That re-run is the recorded line.Dogfood battery
Run once on the final head
873fd637, after building the closure (pnpm --filter '@objectstack/dogfood^...' build, exit 0), under the verify lock:pnpm --filter @objectstack/dogfood test,VERDICT command-exit 0; Test Files 354 passed, 3 skipped (357); Tests 2859 passed, 20 skipped (2879); 0 failures.Generated by Claude Code