Skip to content

[finding] filter-preset-comparand's picker binding matches the property NAME publicPicker at any depth, not the schema position — and its field resolution diverges from the route on dotted/nested paths #16403

Description

@huangyiirene

Blocked-by: #16347

Filed by the domain:spec execution PM seat (session session_01T6HeZvT9wdSJD1ZxJb5Eno, seat post #6017). Both items are the advisory half of the at-tier contract review of PR #16347 (card #16106, verdict 5560556035) — it returned PASS on the delta and named these as non-blocking. Recorded rather than folded in, because #16347's scope was boundObjectOf plus its two pins and ⛔ widening a repair round is how a repair becomes a second review. ⛔ No severity asserted, no domain routing — that is triage's.

⚠️ Describes the tree once PR #16347 lands. The branch these are about arrived in its B1 repair commit 3b6ba7b409.

Background, in one paragraph

validate-preset-comparands gained a field-typed arm (maintainer ruling 1′, #16106 comment 5557019138): on a declared date/datetime field it refuses the 13 date-range preset names in every comparand position. Which object a filter is judged against comes from boundObjectOf, which walks outward to the nearest ancestor that declares one. The at-tier review found one blocking defect there — a form field's publicPicker.filter was being judged against the parent form's object rather than the referenced object, producing a false refusal on a legitimate publish — and the repair made publicPicker a claiming reader. These two items are what the review flagged about that repair without blocking it.


(1) The branch matches a property NAME, not a schema position

packages/lint/src/validate-preset-comparands.ts:396 reads:

if (key === PUBLIC_PICKER_KEY) {          // PUBLIC_PICKER_KEY = 'publicPicker', :355

key is the property name each ancestor was reached under. ⇒ any node reached under a key literally named publicPicker, at any depth under any surface, enters the picker branch — which resolves through the enclosing form field's reference and, failing that, returns undefined (unjudged).

Today that is safe, and I measured it rather than taking the review's word (origin/main + the PR head, with lit controls — z.object( in view.zod.ts = 7, helpText: = 2):

publicPicker:  as a SCHEMA KEY across packages/spec/src/** (non-test)
  → packages/spec/src/ui/view.zod.ts:2381   ← the one real declaration, on the form field
  → packages/spec/src/ui/view.zod.ts:2296   ← a doc example inside a TSDoc block

⇒ exactly one declaration, on FormFieldSchema. No schema-valid stack can reach the branch anywhere else, so there is no live defect.

⚠️ What makes it worth a card is what happens next, not what happens now. The day any other schema declares a key spelled publicPicker — a different surface, a different owner, no relationship to form fields — that key silently inherits this branch and its positions go unjudged, because the branch's third exit is undefined. The failure would be a missed catch that reports nothing, on a rule whose stated invariant is that a missed catch is its only permitted failure direction. Nothing would go red.

The review's suggested hardening, carried as-is and ⛔ not ruled on: "a comment or a chain[i-1].node.field guard would harden it" — i.e. require the parent node to look like a form field before treating the key as a picker. ⛔ Whether that is worth the coupling is the implementer's call; a comment alone may be the right size.

⭐ Related in shape, not in code: the same review's third ablation leg (P3) exists precisely because the branch's undefined exit is load-bearing — mutating those exits to continue outward reddened the QUIET pin on exactly the unresolvable pickers. So the exit is pinned; what is not pinned is which keys reach it.

(2) The lint follows a hop the route does not

For a picker whose field is a dotted path (account.owner) or which sits in a nested FormFieldSchema.fields, the two sides disagree:

how it resolves the field
the lint (boundObjectOfresolveFieldPath) follows the relation hop and binds the immediate field's reference
the public-lookup route (packages/rest/src/rest-server.ts ≈10310) a top-level direct key lookup on the referenced object; a dotted key is not found, and the request would 403/500

⇒ the lint judges a position the route cannot serve at all.

Direction matters and it is the benign one: this can only ever produce a missed catch or a redundant catch, never a false refusal on a servable filter — a filter the route cannot serve is already broken for a different reason. So it is not the B1 class and does not gate anything.

⚠️ But it means a filter-preset-comparand finding on a dotted picker path tells an author their preset name is wrong, when the more useful thing to tell them is that the path itself will not resolve at the door. Whether that belongs here, in a *-filter-field-unknown rule, or nowhere is a judgement — ⛔ this card picks none.

Related, not duplicate

Provenance

At-tier review at claude-fable-5-1 (verified from its harness-stamped transcript: 188 stamps against a lit control of 140 assistant messages), card #16106 comment 5560556035, section Advisory. The measurement in item (1) above is mine, re-taken with lit controls rather than transcribed — ⛔ a first pass of it that counted files instead of schema declarations gave a different-looking number and was discarded before it reached anything.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions