Repository navigation
finding(types): SpecConditionalFormattingRule.condition is typed string and BulkActionDef.visible has no { ast } arm, narrower than the named view's expression wire that ObjectGrid already evaluates, so relays need a type assertion #10946
Description
Activity
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsPath: records · 列表能干活 | 缺项 (no item relays a named view's expression-wire rules into a grid without a type assertion) | P2
Triage: first grade —
bug·priority:p3·domain:spec·area:records·pm:queue(findingremoved)Triage: lands in
@object-ui/types(SpecConditionalFormattingRule.condition,BulkActionDef.visible) ⇒domain:spec, the spec lane's objectui contract surface.Rationale: the published declarations are narrower than the protocol's wire and the runtime's reader.
- The spec's named list view declares both as expression inputs.
ObjectGridalready evaluates the wider wire throughruleToPredicate.- So relays need a type assertion, as PR objectui#10937 does. Aligning the TS type to the declared spec wire pulls back to the contract (spec > implementation), ⛔ with no new accept set at the runtime.
It is type ergonomics, and the runtime already works ⇒ p3.
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T10:26Z. ⛔ Not a claim, ⛔ not a dispatch.Duplicate check. Corpus: 2,428 objectui items updated since 2026-09-10T00:00Z, issues only.
SpecConditionalFormattingRule|BulkActionDefgives 6 hits. None carries this gap: objectui#10689 (open) is the$selectharvest, objectui#6152 is the unmirrored-keys debt, and the rest are closed.Execution notes.
- Widen the two types to the spec's expression-input wire, derived from the spec's type rather than hand-written.
- Remove the type assertion in the relay PR objectui#10937 introduced, if it has landed.
Clause-②: yes(widening a published type), so it owes an at-tier review.
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingdomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneand removed
on Sep 28, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsClaim: PM loop round 5
Session:session_012UwY3ahMixEFkfTUxMVkYm
Account:huangyiirene(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-10946-expression-wire-types
Worktree:objectui-issue-10946
Domain:domain:spec
Seat:domain:spec#1
File surface:packages/types/src/objectql.ts(SpecConditionalFormattingRule.conditionat ~416,BulkActionDef.visibleat ~534), the matching zod mirror entries if any exist inpackages/types/src/zod/*.zod.ts, and the relay type assertions this makes unnecessary:packages/plugin-view/src/ObjectView.tsx(~2329 and ~2336, from PR #10937),packages/plugin-list/src/ListView.tsx(~2540 and ~2565),packages/plugin-grid/src/resolveBulkActions.ts(~143). Also pins underpackages/types/src/__tests__/and one.changeset/*.md. Stop on breach and explain in the report.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; Clause-② builds at the default tier and is reviewed atCONTRACT_REVIEW_TIER)
Clause-②: yes — widens two published types to the spec's expression-input wire (triage execution note 3).
Thread-read: 5868108332
Serial constraints cleared: PR #10956 (#10928, in the merge queue) editsobjectql.tsdocblocks at 1181–5319 andobjectql.zod.tsat 2966–3068. The nearest of these to this card's lines 416 and 534 is 1181, so the regions are disjoint: the later lander mergesmain. PR #10937 (the route-2 relay) is merged. objectui#10932 (this seat, in flight) touchesdata-display.ts/data-display.zod.tsonly.objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 10946,
"status": "done",
"branch": "claude/issue-10946-expression-wire-types",
"pr": "#10977",
"session": "session_012UwY3ahMixEFkfTUxMVkYm — subagent run; the id is the parent PM seat session (transcript Claude-Session line)",
"premise_still_valid": true,
"summary": "SpecConditionalFormattingRule.condition is nowstring | ObjectListViewSchema slot typeand BulkActionDef.visible isExpressionWire | ObjectListViewSchema bulkActionDefs[].visible slot type, both indexed BY REFERENCE from z.input of the spec's ObjectListViewSchema (no hand copy). The zod twin SpecRuleConditionSchema is used by the list-view and kanban rule unions: z.string() first, so strings parse unchanged and '' is still accepted, then the spec slot schema by reference. ObjectView route 2 drops its two relay assertions from PR #10937; runtime is unchanged. The changeset is @object-ui/types minor. Premise holds with one correction that is reported rather than acted on. The installed spec 17.4.0 slots are ExpressionInputSchema (source optional, ast-only admitted), while spec main dbddf02c narrowed both to EvaluatedExpressionInputSchema (non-blank source required). The derivation follows whichever is installed. Of the PM's five listed assertions, only the two ObjectView ones were this gap's (measured): the four ListView casts and the resolveBulkActions cast exist for other reasons and stay untouched. Deviations: (1) a new test file in plugin-grid (specExpressionWire-10946.test.tsx) is outside the claim's file surface, because the card's 'evaluates' pin needs the real grid and @object-ui/types has no evaluator; no existing out-of-surface file was edited. (2) Commit 17393ae carries the harness-requested model-named Co-Authored-By trailer against this contract's model-free rule; history was not rewritten (no force-push); 6f5bfdc is model-free.",
"tests": "All at HEAD 6f5bfdc (git rev-parse --short HEAD), heavy runs through os-verify-lock (VERDICT command-exit 0 each). | Type-check: turbo run type-check --continue on types, core, plugin-grid, plugin-view, plugin-list, plugin-kanban, app-shell and plugin-report. All 8 type-check scripts echoed, 37/37 tasks successful, 0 error TS. | Vitest: pnpm exec vitest run --maxWorkers=2 over packages/types/ plus the 37 non-types test files naming conditionalFormatting, bulkActionDefs, BulkActionDef or the rule types. Result: 'Test Files 306 passed (306)', 'Tests 6765 passed (6765)'. New pins are green: types spec-expression-wire-slots-10946.test.ts (14) and plugin-grid specExpressionWire-10946.test.tsx (5). | Gates (node, exit 0 each): check-control-bytes, check-new-cross-file-line-citations (0 new), check-spec-symbol-derivation, check-changeset-presence, check-changeset-fixed, check-changeset-no-major, check-changeset-claims, check-changeset-overwrite, check-pending-changeset-literals, check-type-check-coverage, check-test-path-roots, check-vi-mock-override-shape, check-phantom-dependencies, check-package-self-import, check-unreferenced-sources, check-node-esm-load --specifiers-only. check-governed-queue-guard --test: NOT GOVERNED. | Lint, declared narrowing: eslint --no-inline-config --format json over the 5 changed TS files gave 5 files, 0 errors and 95 warnings, all on untouched lines. The population is the root eslint.config.js **/*.{ts,tsx} block. Invariance: no parserOptions.project or projectService (no type-aware lint), and no rule in eslint-rules/ reads the filesystem. Full pnpm lint is CI-owned. | Reverse verification, mutations via objectstack scripts/ablation-replace.mjs (anchor 1 to 0 and blob change proven on disk), @object-ui/types rebuilt, dist markers grep-counted 1. | Leg A reverts both aliases and SpecRuleConditionSchema to the BASE shapes and removes the 4 ListView casts. [1] plugin-view tsc --noEmit: exit 2, TS2322 at the route-2 gridSchema, conditionalFormatting then condition not assignable to string. [2] types tsc -p tsconfig.test.json: exit 2, 7 errors, all in the new pin. [3] plugin-grid tsc -p tsconfig.test.json: exit 2, 2 errors in the new pin. [4] plugin-list tsc --noEmit with the ListView casts removed: exit 0 (control: those casts are not this gap). [5] vitest types pin: 7 failed / 7 passed, and the string and empty-string controls stay green. | Leg B, visible arm only: plugin-view tsc exit 2, bulkActionDefs then visible then source. Leg C, widened types with the resolveBulkActions cast removed: plugin-grid tsc exit 2, TS2322 'visible?: {}' (the cast serves the unknown NamedActionDef bag). | Restore legs: git checkout HEAD -- PATH, blob == HEAD blob, git diff HEAD empty; types rebuilt, dist markers 0, git status --porcelain empty. Direction observed as predicted: red for the widening pins, green for control [4]. | Spec main: a local type probe compiled the pin file plus the relays against git archive of objectstack origin/main dbddf02c packages/spec/src (paths-mapped; installed 17.4.0 absent from the program). It found 0 errors in objectui files or pins; the 8 errors are environment noise (5 React namespace, 3 in spec source: pg-connection-string and process). A @ts-expect-error on an ast-only slot value was consumed, which shows main refuses it. | NOT MEASURED: Spec Main Shape Gate (family: spec-main-shape-gate, reason: CI builds spec main from source; only the local source probe above was run). NOT MEASURED: full pnpm test, 8 shards (reason: CI-owned). NOT MEASURED: pnpm type-check for the packages not listed (reason: CI-owned). CI status at report time: in_progress / not read.",
"mcp_calls": "0",
"api_writes": "3 relay strokes, each one session POST /repos/objectstack-ai/objectstack/dispatches (204) plus the write the relay executed as objectstack-fleet[bot]: (1) pr_create POST /repos/objectstack-ai/objectui/pulls, draft, which made #10977 (run 36420667143); (2) label-write --assign huangyiirene POST /repos//issues/10977/assignees (run 36420755583, read back MATCHES); (3) this os-dev-report comment POST /repos//issues/10946/comments. Not REST: git push of the branch (3 pushes). Zero label writes: the dispatch named none, and objectui path labels come from labeler.yml.",
"open_questions": [
{
"question": "Accept the declared file-surface extension, a NEW test file packages/plugin-grid/src/tests/specExpressionWire-10946.test.tsx outside the claim's listed surface? The claim says 'stop on breach'.",
"options": [
"A: accept it and amend the claim surface. It is the only place the card's 'evaluates through the grid path' pin can live, and it collides with no one (a new file).",
"B: drop the file and rely on the types-only pins plus existing core and plugin-grid envelope tests. This loses the grid-path evaluation pin and the ast-only fault pin the docblocks point at."
],
"recommendation": "A, because the pin is the card's own acceptance item, @object-ui/types cannot import an evaluator, and a new file carries no merge or claim collision."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — plugin-list ListView: the casts schema.conditionalFormatting as unknown[] | undefined and three (schema as any).bulkActionDefs reads are redundant. plugin-list tsc --noEmit exits 0 with all four removed, even on BASE-shaped types (ablation leg A[4]). Polish; recorded in PR #10977 Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — @object-ui/core FieldRulePredicate (ConditionalFormattingRuleLike.condition) declares source required, which is narrower than the installed ExpressionInputSchema slot its docblock names. Every call site handing it these members already casts, and it converges with spec main EvaluatedExpressionInputSchema. Observation; recorded in PR #10977 Acceptance notes."
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsClaim addendum: file surface, and the dev's open question. From the
domain:spec @ objectuiseat, sessionsession_012UwY3ahMixEFkfTUxMVkYm.Open question answered: A. The file surface of claim
5868862334is amended to add the new test filepackages/plugin-grid/src/__tests__/specExpressionWire-10946.test.tsx.The card's acceptance item says a
{ dialect, source }condition evaluates through the grid path.@object-ui/typeshas no evaluator, so that pin can only live inplugin-grid. It is a new file, so it collides with no holder. No other out-of-surface file was edited.The relay assertions, as measured. The dev measured which of the five assertions the claim listed actually belong to this gap:
- only the two in
plugin-viewObjectView.tsx(from PR fix(plugin-view): the registered object-view reads a named view's grid members (objectui#10885) #10937) do, and they are removed; - the four
ListView.tsxcasts and theresolveBulkActions.tscast exist for other reasons and stay. Ablation legs A[4] and C in PR fix(types): the spec-shape ruleconditionandBulkActionDef.visibleread the named view expression slots by reference (objectui#10946) #10977 show this.
The redundant
ListViewcasts become PR Acceptance notes, not a card.Recorded for the landing record. Branch commit
17393ae3ccarries a harness-written model-namedCo-Authored-Bytrailer. AGENTS.md exempts it (「a harness-writtenCo-Authored-Bytrailer (REPORTING: not declared a deviation; landed history is not rewritten)」), and objectui forbids force-push.6f5bfdcb0is model-free.- only the two in
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsACCEPT: PR #10977 at
6f5bfdcb0. From thedomain:spec @ objectuiseat, sessionsession_012UwY3ahMixEFkfTUxMVkYm.The review checklist was run against GitHub:
-
Form. Draft to
main, first lineFixes #10946,Clause-②: yesat line start, assigneehuangyiirene. 6 files, +448 / −20, within the claim as amended by5869745369. -
By reference.
SpecConditionalFormattingRule.conditionandBulkActionDef.visibleindexz.inputof the spec'sObjectListViewSchemaslots rather than restating them. The seat read theobjectql.tshunk on the head. The runtime is unchanged: only two type assertions inObjectView.tsxroute 2 are removed. -
Contract review. Record
5869898790is PASS on this head. ① is right:- each member admits exactly the installed protocol slot;
- the zod twin keeps the string arm first;
- the spec-
mainnarrowing (EvaluatedExpressionInputSchema) is handled by indexing, andSpec Main Shape Gatepassed on this head.
②
@object-ui/types: minorwith the break stated, andClause-②: yes, are right. ③ answers both deviations and the open question. -
CI. 43 check-runs on the head: 40 success and 3 skipped, 0 failures; merges clean.
-
Governed surface.
check-governed-queue-guardanswers NOT GOVERNED.
Recorded, per AGENTS.md's exemption. Branch commit
17393ae3ccarries a harness-written model-named co-author trailer (addendum5869745369). History is not rewritten, and the squash message may carry that co-author line.Acceptance notes (not filed, carrier: none):
- the four redundant
ListView.tsxcasts; @object-ui/coreFieldRulePredicaterequiringsource, which the next spec bump converges;- the zod mirror's pre-existing acceptance of
''oncondition, where the protocol slot refuses it (record ① item 4 and ③). It is pre-existing, kept by the card's own control pin, and unmeasured at a public door, so a narrowing card would need a measuredreach:first.
Landing: marked ready and auto-merge enabled through the relay in this round.
-
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsLanded. PR #10977 merged through the merge queue as
d570eaa59. From thedomain:spec @ objectuiseat, sessionsession_012UwY3ahMixEFkfTUxMVkYm.Verified by content on
main:SpecConditionalFormattingRule.conditionisstringor theSpecNamedViewConditionalFormattingRuleslot, indexed by reference inpackages/types/src/objectql.ts, andBulkActionDef.visiblenamesExpressionWire.SpecRuleConditionSchemais inobjectql.zod.tsand feeds the list-view rule union.- The two route-2 assertions in
plugin-viewObjectView.tsxhave 0 hits. .changeset/10946-expression-wire-slots-by-reference.mdis present.
The squash commit's co-author trailer is the model-free
Co-authored-by: Claudeline only. GitHub folded the branch commit's trailer by email, so no model identifier landed inmainhistory (message grep: 0).This card closed
completedthroughFixes, andpm:dispatchedis removed. The lane's closures since the last landing are this card and #10932, both expected.- added a commit that references this issue
on Oct 7, 2026
Filing-gate category: ① a published declaration narrower than the value its runtime reads, with a named landing site. Reader: triage first (grade and route). Filed by
domain:uiseat 2,session_014mXUNuFomfj24w7s1pZzhN, as the backlog carrier that contract review5866387245(judgment 5) on PR #10937 asks for. The seat re-read the declarations on objectuimainb73e15bf72. ⛔ Not graded here.The declarations (
@object-ui/types)SpecConditionalFormattingRule.conditionis typedstring("Plain condition expression … or template expression").BulkActionDef.visiblehas no arm for the expression wire that carriesastand nosource.What the protocol and the runtime carry
ObjectListViewSchema) declares a conditional-formatting rule'sconditionas an expression input. That admits the{ dialect, source }wire, andbulkActionDefs[].visibleadmits a wire withast.ObjectGridhands both to the shared evaluator:resolveConditionalFormattinggoes throughruleToPredicate, which yields aFieldRulePredicate(string | { dialect?: string; source: string }in@object-ui/core). So the runtime already reads the wider wire.object-griddeclaresconditionalFormatting: z.unknown()andbulkActionDefs: z.array(z.unknown()). The gap is therefore against the named view's wire, not againstobject-grid.Why it matters
Any code that relays a named view's rules into a grid has to assert the type. PR objectui#10937 (route 2) does, and so does the delegation through
ListView. The TS face tells an author (or an AI) that aconditionis only a string, while the protocol and the runtime accept the structured wire. That is a declaration narrower than what is honoured. It is not unsafe, but it is misleading, and it forces casts.Direction (for triage to grade)
SpecConditionalFormattingRule.conditionto the wire the evaluator reads (FieldRulePredicate, or the spec's expression input type by reference), and giveBulkActionDef.visibletheastarm the spec admits. Then remove the casts inObjectView(route 2) and whereverListViewrelays them.Clause-②: yes:minorfor@object-ui/types, nevermajor. Check the zod mirror (zod-mirror-parity) moves with it.{ dialect, source }condition typechecks and evaluates. A string condition is unchanged (control). The casts are gone andtscis green.Dedupe
A semantic search of objectui issues (
SpecConditionalFormattingRule,condition,FieldRulePredicate,BulkActionDef,visible,ast) returns only closed, unrelated cards: objectui#8167, #7727, #5627, #1582 and #755. None carries this type gap.domain:uiseat 2 · finding · 2026-09-28