Skip to content

fix(ui-core): read a bare-string predicate as CEL shorthand, as the spec and server do - #27584

Draft
objectstack-fleet[bot] wants to merge 8 commits into
mainfrom
claude/project-thread-054wyf
Draft

objectstack-fleet[bot] wants to merge 8 commits into
mainfrom
claude/project-thread-054wyf

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx

Requested by Sales · project thread

Fixes #27457

Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: follow-up #27587

维护者速读

  • 改了什么:控制台把「裸字符串」谓词(导航 visible、页面 visibleWhen、动作 visible/disabled 等)当作 CEL 求值,与 { dialect: 'cel' } 信封走同一个引擎;只有 ${…} 模板仍走旧 JS 求值器。
  • 为什么改:spec 把裸字符串定义为 CEL 简写,服务端也按 CEL 读;控制台却按旧 JS 语法读,'org_owner' in current_user.positions、size(page.items) > 0 这类 CEL 写法在控制台报错,导航项对所有人隐藏、页面区块不渲染(ADR-0032:只有一种表达式语言)。
  • 风险与回滚:只依赖 JS 语法的裸字符串谓词(===、.includes()、.length、读不存在的 data.* 键)现在会故障并按各入口原有故障策略处理(导航/显示门隐藏,disabled 置为禁用);仓内无此类已编写元数据(实测见下)。回滚即 revert 本 PR。
  • 先看:ui/packages/core/src/evaluator/ExpressionEvaluator.ts(if (!unwrappedEnvelope) 分支)、ui/packages/core/src/evaluator/predicateInput.ts(字符串分支)、ui/packages/core/src/evaluator/listConditional.ts(行谓词路由)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/providers/__tests__/navigationVisibility.bareCel-27457.test.ts packages/react/src/__tests__/SchemaRenderer.bareCelVisibleWhen-27457.test.tsx,预期 8 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

The spec defines a bare string in a predicate slot as shorthand for { dialect: 'cel', source } (ExpressionInputSchema / EvaluatedExpressionInputSchema), and the server reads it that way. The console read it on the legacy JS grammar, so a CEL-only predicate faulted: a nav entry visible: "'org_owner' in current_user.positions" was withheld from everyone, and a page block visibleWhen: "size(page.items) > 0" never rendered, while the same source as an envelope worked.

  • ExpressionEvaluator.evaluateCondition (@object-ui/core): a bare string now takes the CEL route (evaluateCelCondition), the same one a cel envelope takes, with the same onFault / throwOnError policy. Only a ${…} template and the source of an envelope whose dialect is not cel stay on the legacy path.
  • toPredicateInput: a bare string normalizes to { dialect: 'cel', source } instead of being wrapped as ${…}; an already-${…} string is returned untouched as before.
  • evalRowPredicate (listConditional.ts): only a ${…} template is routed to the legacy engine; every other bare string is CEL. The one-time legacy-syntax warning still fires for any legacy marker.
  • Comment-only updates in ExpressionProvider.tsx and SchemaRenderer.tsx where they described the old bare-string route.

Why this is the normalization point: evaluateCondition is the single funnel every console predicate reader reaches (SchemaRenderer, ExpressionProvider's nav and visibility gates, ActionEngine, the hooks), and several of them hand it the authored value without normalizing first. Reading the bare string there is the console's equivalent of the server's normalizeExpression input step; toPredicateInput emits the same envelope for the callers that normalize first, so both orders reach one engine. No legacy path remains for the shorthand.

Branch name: this card was worked in a platform-assigned card-thread branch (claude/project-thread-054wyf), not the claude/issue-27457-SLUG shape; the claim names this branch.

Tests

  • New acceptance tests:
    • app-shell/src/providers/__tests__/navigationVisibility.bareCel-27457.test.ts: the nav visible bare string 'org_owner' in current_user.positions is true for the holder and false for the member, with no fault line; the envelope control reaches the same verdicts; evaluateVisibility agrees; a legacy-only === spelling is withheld with one report, and its == control is shown.
    • react/src/__tests__/SchemaRenderer.bareCelVisibleWhen-27457.test.tsx: page visibleWhen size(page.items) > 0 is shown with items and hidden without, 'org_owner' in current_user.positions shows the holder and hides the member, the envelope control agrees, and a legacy-only === spelling is withheld with one report.
  • Pin sweep: every pin of the "bare strings stay legacy" window was flipped to assert the CEL verdict substantively (fault reason text, report count, shown/hidden state): ActionEngine.visibility, ActionRunner.disabledGate, declaredPredicate, predicateInput, listConditional, actionPredicate.parity, useExpression, record-alert.rowBinding, record-block-record-reach, SchemaRenderer.nodeGateDataPredicate, SchemaRenderer.productionPredicateFaultWarning, SchemaRenderer.disabledGateFaultDiagnostic, DeclaredActionsBar. Legs that pinned the adapter-only data.* diagnostic keep their coverage through the ${…} template spelling, which still reads on the legacy engine.

Acceptance notes

  • Narrowing: a bare-string predicate that relies on JS-only syntax (===, !==, ?., ??, .includes(…), .length, or reading a data.* key the scope does not hold) now faults instead of evaluating. Measured over the repository: 1604 bare predicate literals, 127 do not parse as CEL; the 54 outside tests are i18n label strings, lint-script fixture prose, regex-escaped zod description examples and one className, none of them an authored predicate. The 73 in tests are fixtures, flipped above where they pinned the legacy reading. The changeset carries a BREAKING note that points authors to the CEL spelling and lists no FROM-to-TO rewrites (ADR-0087 disposition not-required (no-migration-prescription)); the per-spelling rewrite guidance lands in the skills under follow-up skills/objectui teaches JavaScript-syntax bare predicates (===, !, raw JS operators) that the console will read as CEL after #27457 #27587.
  • The adapter-only data.* diagnostic (objectui#5687) fires only on a clean evaluation, so it is now reached only through ${…} templates; a bare data.* miss reports on the fault leg instead (pinned).
  • On the throwOnError callers (dev-mode visibility gate, DeclaredActionsBar), a CEL fault reports the generic CEL predicate failed to evaluate: SOURCE line, while onFault callers report [kind] message (for example No such key: status). This split already existed for envelopes and is unchanged here.
  • In-place fix declared: ui/packages/core/src/evaluator/listConditional.ts is outside the claim's listed file surface. Same defect class (its row-predicate router sent bare strings with legacy markers to the JS engine), mechanical, no other claim on the file, same gate families; the ablation below shows its test turns red without it.
  • Out of scope, not changed: ExpressionEvaluator.evaluatePlainCondition still reads a bare string on the legacy path.

Verification

Ablations (each through scripts/ablation-replace.mjs under the verify lock, restored to HEAD after)
  • ExpressionEvaluator.ts anchor if (!unwrappedEnvelope) { replaced with if (false) { (legacy route restored): 6 failed, all in the two acceptance files (nav 3, page 3).
  • predicateInput.ts string branch replaced with return wrapIfBare(value);: 5 failed across ActionEngine.visibility, declaredPredicate, predicateInput, useExpression.
  • listConditional.ts router source.includes('${') replaced with isLegacyDialectSource(source): 1 failed (the bare === CEL parse-fault report).
Local test runs
  • @object-ui/core plus every edited and new test file, one run under the verify lock: 227 files, 4326 passed, 27 skipped, 2 failed in SchemaRenderer.nodeGateDataPredicate (dev-line reason text); those two assertions were corrected and the file re-run: 24/24 passed.
  • Type-check (type-check) of @object-ui/core, @object-ui/react, @object-ui/app-shell, @object-ui/plugin-detail, @object-ui/console: exit 0.
  • eslint on changed files: 0 errors.
Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 34 commands on this diff; each was run with its exit code recorded, plus pnpm check:changeset-fixed for the .changeset roster. All 35 exit 0. --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3

…pec and server do

Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3
Co-authored-by: Claude <noreply@anthropic.com>
A bare-string predicate is CEL shorthand, so every pin that relied on the
legacy JS reading of a bare string now asserts the CEL verdict: `===` and
data-key misses fault and take each surface's fault policy, while the
adapter-only `data.*` legs keep their coverage through `${…}` templates.
Adds the changeset and refreshes two SchemaRenderer comments.

Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3
Co-authored-by: Claude <noreply@anthropic.com>
The console stops accepting bare-string predicate spellings only the
legacy JavaScript grammar reads, so the changeset declares the narrowing
arm and kind, takes a minor bump, carries the BREAKING banner and states
its ADR-0087 disposition.

Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3
Co-authored-by: Claude <noreply@anthropic.com>
CI surfaced three suites that still pinned a bare string on the legacy
JS grammar: the page header's legacy-dialect fallback, the useCondition
fault reasons and host-data case, and the record alert's loading-frame
diagnostic filter. Each now asserts the CEL verdict with a CEL control.

Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 827769ada2f6a80200e541a8e9abb97397f58a79
Local-runs: none
Scope: ui/packages/core/src/evaluator/ExpressionEvaluator.ts, ui/packages/core/src/evaluator/predicateInput.ts, ui/packages/core/src/evaluator/listConditional.ts, ui/packages/app-shell/src/providers/ExpressionProvider.tsx, ui/packages/react/src/SchemaRenderer.tsx, ui/apps/console/src/tests/record-block-record-reach.test.tsx, ui/packages/app-shell/src/providers/tests/navigationVisibility.bareCel-27457.test.ts, ui/packages/app-shell/src/views/tests/DeclaredActionsBar.test.tsx, ui/packages/components/src/tests/page-header-predicate-dialect.test.tsx, ui/packages/core/src/actions/tests/ActionEngine.visibility.test.ts, ui/packages/core/src/actions/tests/ActionRunner.disabledGate.test.ts, ui/packages/core/src/evaluator/tests/declaredPredicate.test.ts, ui/packages/core/src/evaluator/tests/listConditional.test.ts, ui/packages/core/src/evaluator/tests/predicateInput.test.ts, ui/packages/plugin-detail/src/renderers/tests/record-alert.loadingFrameDiagnostic.test.tsx, ui/packages/plugin-detail/src/renderers/tests/record-alert.rowBinding.test.tsx, ui/packages/react/src/tests/SchemaRenderer.bareCelVisibleWhen-27457.test.tsx, ui/packages/react/src/tests/SchemaRenderer.disabledGateFaultDiagnostic.test.tsx, ui/packages/react/src/tests/SchemaRenderer.nodeGateDataPredicate.test.tsx, ui/packages/react/src/tests/SchemaRenderer.productionPredicateFaultWarning.test.tsx, ui/packages/react/src/hooks/tests/actionPredicate.parity.test.tsx, ui/packages/react/src/hooks/tests/useCondition.canonSpelling.test.tsx, ui/packages/react/src/hooks/tests/useExpression.test.ts + packages/spec/src/shared/expression.zod.ts, packages/formula/src/normalize.ts, ui/packages/core/src/evaluator/fieldRules.ts, ui/packages/react/src/hooks/useExpression.ts, ui/packages/core/src/actions/ActionEngine.ts, ui/packages/core/src/actions/ActionRunner.ts, ui/packages/components/src/renderers/layout/containers.tsx, ui/apps/console/src/pages/settings/SettingsView.tsx, ui/packages/app-shell/src/utils/managedByEmptyState.ts, scripts/check-adr-0087-registration.mjs, docs/adr/0032-unified-expression-layer.md, .claude/agents/os-dev.md, skills/objectui/guides/schema-expressions.md, skills/objectui/rules/protocol.md, packages/qa/dogfood (coupling read)

Inputs: card #27457 body and its 5 comments (triage 6491385506, dispatch 6495413455, claims 6495627500 and 6498534084, dev report 6498493316), PR #27584 body, its 24-file list and the net diff against origin/main at the head, the 52 check-runs on the head. Everything below is read off the tree at the head, not off the PR body.

① Derived judgments

Accept-set, console reading of a BARE string in a predicate slot (nav visible, node visibleWhen / visible / hidden / disabled, action visible / disabled / condition, row predicates, settings visible, toolbar-action visible):

  • Newly ACCEPTED (evaluated instead of faulting): every CEL-only construct — in over a list, size(), has(), .contains(), today(), CEL .? selection, the spec's own example 'org_owner' in current_user.positions, the card's size(page.items) > 0. Each of these was always accepted by the spec and the server: ExpressionInputSchema / EvaluatedExpressionInputSchema transform the string arm to { dialect: 'cel', source } (packages/spec/src/shared/expression.zod.ts L190-193, L269-273) and normalizeExpression parses that envelope and compiles it on ExpressionEngine (packages/formula/src/normalize.ts L33-62). RIGHT — the console joins the contract it already declared.
  • Newly FAULTED (were evaluated on the JS grammar): ===, !==, ?., ??, ternary a ? b : c, typeof, JS methods (.includes(, .length, .filter(, .map(, .toUpperCase( …), single-param arrows, and a read of a key the bound map does not hold (data.status on a data: {} host read undefined and compared clean; CEL answers No such key: status). None of these was ever accepted by the spec or the server: the string arm is CEL by definition and the CEL compiler refuses them at objectstack compile / publish. RIGHT, and correctly declared as a narrowing of what the CONSOLE tolerated, not of the protocol. Measured on the head: a repo-wide grep for a predicate key followed by a string carrying === / !== / .includes( / .length / ?. outside tests hits 2 lines, both in skills/objectui (the lines follow-up skills/objectui teaches JavaScript-syntax bare predicates (===, !, raw JS operators) that the console will read as CEL after #27457 #27587 names); the dev's "0 authored non-test predicates" holds.
  • The missing-key consequence per slot, pinned by the flipped tests: a show-key node gate stays hidden (same verdict, now a fault line instead of the adapter-only dev line); a bare hidden gate goes from shown to concealed; a bare disabled gate goes from enabled to disabled; the fail-soft useCondition leg goes from false to true; the nav gate withholds. Each is the slot's pre-existing fault direction (objectui#13299, pm 席位把 AskUserQuestion 当裁决通道,维护者不在场即整席人质——规则:先落 needs-user-decision 卡,弹窗仅作在场加速器 #11212, docs(rest): qualify the three residual ADR-0112 citations to the code axis #12700), applied to a predicate that the server would also fault on. RIGHT.
  • Public surface of @object-ui/core (re-exported by @object-ui/react): toPredicateInput(string) now returns { dialect: 'cel', source } instead of '${string}'; the EvaluatorPredicateInput type is unchanged and both members were already in it; a ${…} string is still returned as-is; a non-cel envelope's source is still wrapped. Idempotence holds by value (the parity test moved from toBe to toEqual, honestly: the envelope branch re-emits a fresh equal object). RIGHT, and named in the changeset.
  • ExpressionEvaluator.evaluateCondition: branch order on the head is unevaluable → cel envelope → unwrap other envelope (remembered) → blank → absent → ${ template → bare string to evaluateCelCondition → legacy only for the unwrapped non-cel source. A bare string therefore reaches the same evalFieldPredicate seam as the envelope with the same onFault / throwOnError contract, and the same bag (record pinned when present, the whole bag as extra, current_user permissions and capabilities ridden on the subject). RIGHT. The blank bare string ' ' now arrives as the blank cel envelope and is answered by answerBlankGate (diagnosed "no gate", ADR-0137 D4) — one report, one dedupe key for both spellings. RIGHT.
  • evalRowPredicate: a bare non-template string goes to the CEL path; isLegacyDialectSource still fires its one-time prescription line for any legacy marker, so a bare === row predicate prints the prescription AND the engine's fault line, once each — two lines, both true, the first carrying the rewrite. Acceptable.
  • evaluatePlainCondition (exported from @object-ui/core, no console caller, its own tests pin ===) still reads a bare string on the JS grammar. Not in the claim surface; declared out of scope in the PR. See ③.
  • Comment-only edits in ExpressionProvider.tsx and SchemaRenderer.tsx: each describes the route the code now takes; the isCelEnvelope hoist guard's rationale is correctly restated (stripping the envelope no longer changes engines). RIGHT.

② Semver level

  • Changeset .changeset/27457-bare-string-predicate-cel.md: '@object-ui/core': minor, Clause-②: no (narrowing), Metadata-protocol: narrowed, a BREAKING banner, exactly one adr-0087 marker. PR body carries the same two declaration lines plus Skills-docs: follow-up #27587. Lines match on both carriers.
  • Level: narrowed is a kind other than none on a package the diff moves ⇒ at least minor (AGENTS.md Post-Task Checklist step 3). minor is the right level. Only @object-ui/core is bumped; the other touched packages ship comments and tests only, and dependents take the internal-dependency bump. Right.
  • ADR-0087 disposition not-required (no-migration-prescription): judged against step 3 — a FROM-TO mapping is owed when the diff removes or renames something an author can write under the protocol (a spec key, an export, a config field). Nothing protocol-legal moves: the spellings that now fault were refused by the spec's string arm and by the server's compile step before this PR, so no stored row the server accepted changes meaning and objectstack migrate meta has nothing to rewrite. The gate's own honesty check for this category (body must carry no prescription — scripts/check-adr-0087-registration.mjs L147-153, arrow / ## Migration / FROM-TO label detectors) is satisfied: the body has no arrow and no migration heading. HOLDS. The teaching of the CEL spellings is correctly routed to the skills follow-up rather than into the CHANGELOG.
  • One prose defect (should-fix, below): the changeset's last BREAKING sentence tells an upgrader that a predicate "that must keep the legacy reading can be spelled as a ${…} template". That is a console-only spelling the spec does not model as a predicate and the server will not read; shipping it in CHANGELOG.md advertises a second dialect the triage direction ("no legacy path") set out to remove, and the PR body's "The changeset carries a rewrite guide" misdescribes a body that, correctly for its disposition, carries none.
  • Check-runs on the head at read time: 52 total — 13 success, 11 skipped, 28 in_progress. Success includes Check Changeset, PR-body guards, Governed Surface Queue Guard, Test Core, Type Check · source gates / workspace / debt ledger, Dogfood Regression Gate (its 3 shards path-skipped), Live E2E UI, Build UI Deps. In progress: all 16 Test UI shards, Type Check UI, Type Check · consumer gates, Build & E2E UI, Lint UI, and the 8 root Lint · lanes (the one that runs check-adr-0087-registration among them). Their conclusions are the gate verdicts for the pin sweep, the ui type-check and the ADR-0087 marker; this record does not pre-empt them — the queue waits for green.

③ Boundary flags

Dev report 6498493316 is on head 223d913f; the head of record adds 14d03c24 (changeset: the narrowing arm, BREAKING banner, disposition) and 827769ad (three more pin flips: page-header-predicate-dialect, record-alert.loadingFrameDiagnostic, useCondition.canonSpelling). Both are judged from the diff above; the three flips are substantive (verdict + paired CEL control + reason text), not weakened.

  • Deviation 1 — in-place fix of listConditional.ts outside the claim surface: ANSWERED. The superseding claim 6498534084 adds the file to the surface; same defect class, ablation leg 3 red without it.
  • Deviation 2 — full ui vitest sweep stopped at 86 min, 8 failing files all flipped: ANSWERED BY THE GATE. The 16 Test UI shards on the head are the measurement; in progress at read time.
  • Deviation 3 — dogfood battery "n/a: no ! in the title and no (narrowing) arm": STALE ON THE HEAD. The arm was added at 14d03c24, after the report; .claude/agents/os-dev.md L274 owes the full battery once on the final head whenever the Clause line carries (narrowing), and CI's Dogfood shards are path-skipped on this head. Grade: should-fix, not FAIL — packages/qa/dogfood has zero @object-ui / ExpressionEvaluator references, so this diff cannot move it; the seat either runs it once on 827769ad and records dogfood_battery, or records that reading as the exemption.
  • open_questions: none. out_of_scope_findings: evaluatePlainCondition legacy reading, carrier "none". Should-fix: it is a published export whose bare-string reading contradicts the spec (a residual second grammar on the public surface); PD chore: version packages #10 wants a carrier for a contract violation — file a card, do not expand this diff.
  • Skills deferral (Skills-docs: follow-up #27587): ACCEPTABLE, with a condition. skills/** is Tier H (PD feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14), so it could not land inside a default-tier card thread; the spelling follow-up #N is the claim template's; skills/objectui teaches JavaScript-syntax bare predicates (===, !, raw JS operators) that the console will read as CEL after #27457 #27587 is open, names the exact lines (schema-expressions.md L10, L83-89, L254, L335-336; protocol.md L17) and carries the FROM-TO teaching the changeset rightly does not. Until it lands, skills/objectui teaches ===, ?., ??, .includes() as condition syntax that the console now faults on — the card's own "should land with or right after fix(ui-core): read a bare-string predicate as CEL shorthand, as the spec and server do #27584" is the condition the seat holds.
  • Seat premise re-measured on the head: evaluateCondition routes a bare string to CEL at L434-436; the legacy branch at L441 is reachable only with unwrappedEnvelope === true. The dispatch's acceptance (nav visible and page visibleWhen as bare CEL evaluate on CEL; restoring the legacy route turns the tests red) is pinned by the two new acceptance files, each with a polarity pair, an envelope control and a legacy-only === counter-case.

④ Design judgment

Held against the code it cites and the accepted scenario; tried to break on fault policy, missing roots, list paths and double evaluation.

  • ExpressionEvaluator.ts — HOLDS. The funnel claim is true on the tree: every console predicate reader reaches evaluateCondition (SchemaRenderer L1455/L1476/L1592, ExpressionProvider L598/L669/L682, ActionEngine L287, ActionRunner L1544/L1577, useCondition L222/L241, containers.tsx L762/L842, SettingsView L45, managedByEmptyState L84, listConditional L321), and several hand it the raw authored value, so resolving the shorthand here is the console's equivalent of the server's input step. Roots: the nav bag (buildExpressionScope) binds current_user / user / features; the node bag (buildNodeEvaluationScope) binds record, current_user, page.VAR; SettingsView binds data — the scenario's current_user.positions and page.items resolve, and the tests prove it with opposite verdicts on the two polarities. Fault policy per slot is untouched: onFault single call on the fail-soft legs, the throwOnError two-call probe only where it already ran for envelopes (ActionEngine, DeclaredActionsBar, dev-mode node gate, nav second ask on fault). No new double evaluation: a bare string now pays exactly what its envelope paid. The throwOnError message split (generic CEL predicate failed to evaluate: SOURCE vs onFault's [kind] message) pre-dates this PR and is pinned as such. Non-cel envelopes: template envelopes are produced only for text slots (actionCopyTemplate, record-title, titleFormat), so the kept legacy route for an unwrapped non-cel source is dormant and harmless.
  • predicateInput.ts — HOLDS. The string branch emits the spec's own normalized value; wrapIfBare survives only for the non-cel envelope branch; hasDeclaredPredicate (derived from this normalizer) keeps its three states — the blank bare string is now the blank cel envelope the evaluator diagnoses, which the declaredPredicate and ActionRunner.disabledGate tests re-pin.
  • listConditional.ts — HOLDS. source.includes('${') is the only legacy router; the row is still pinned as record after the host spread on both paths; warnLegacyDialect keeps firing for any marker so the prescription channel survives the route change; the fail-closed route reports [parse] for a bare === and [legacy] only for a template (pinned).
  • ExpressionProvider.tsx, SchemaRenderer.tsx — comment-only; each matches the route the code now takes. The nav gate's "fault ⇒ withheld" and the node gate's "fault ⇒ withheld" are what turned the original bug into "hidden for everyone"; with the bare string on CEL those same policies now withhold only a predicate the server would also refuse.
  • Pin sweep — HOLDS. Every flipped pin asserts the new semantics' substance: the CEL verdict on a matching row, the paired == / .contains() control, the report count, and the reason text (No such key: status, Unknown variable: bogus_unbound_field, Reason: [parse], CEL predicate failed to evaluate: …); legs whose subject was the adapter-only data.* diagnostic keep it through the ${…} spelling, the one that still evaluates cleanly against the wrong object, and gain a sibling case pinning where the bare spelling went. No pin was deleted or weakened to "old assertion gone". The two fixture === in record-block-record-reach are rewritten to ==, which is the fix the card asks of authored metadata.

Findings (none must-fix):

  • should-fix — changeset BREAKING paragraph: drop or reword "can be spelled as a ${…} template"; point the upgrader at the CEL spelling (==, in, .contains(), size(), has()) without a FROM-TO list, so the disposition stays honest and the CHANGELOG stops advertising a console-only dialect. Fix the PR body's "carries a rewrite guide" in the same stroke.
  • should-fix — run the dogfood battery once on the final head (or record the zero-coupling reading as the exemption) and refresh the report's head / dogfood_battery fields.
  • should-fix — file a card for evaluatePlainCondition's legacy bare-string reading (published export, no console caller) instead of carrier "none".
  • condition — land skills/objectui teaches JavaScript-syntax bare predicates (===, !, raw JS operators) that the console will read as CEL after #27457 #27587 with or right after this PR; until then the skill and the runtime disagree on the operator table.

Implemented-by: claude/project-thread-054wyf
Reviewed-by: session_01L6bhgXU26UBEjd1KKHgNEx

VERDICT: PASS

Rendered 2026-10-29T14:39Z.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Regen-provenance: 6499697983 · 827769ada2f6a80200e541a8e9abb97397f58a79 → f9ef8e90d2642f0cdebeb2af1c78b86829acc4f7 · git diff --name-only 827769ad f9ef8e90 -- . ':(exclude).changeset/**' ':(exclude)**/CHANGELOG.md' ':(exclude)content/docs/**' ':(exclude)apps/docs/**' → (empty)

The one commit after the reviewed head (f9ef8e90, the record's first should-fix) touches .changeset/27457-bare-string-predicate-cel.md alone, a prose face outside the review surface, so record 6499697983 keeps governing this head. The changeset sentence is the ACCEPT's to check line by line against the diff.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx

席位核对 · ACCEPT · 2026-10-29T15:09Z

Head-sha: f9ef8e90d2642f0cdebeb2af1c78b86829acc4f7 · card #27457 · claim 6498534084 · dev report 6500449699.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

1 participant