Skip to content

search: no declarative way to keyword-search by a related record's title ($searchFields rejects cross-object paths) #4840

Closed

Description

@baozhoutao

从 #4830 拆出的能力缺口(该 PR 只做构建期校验对齐,本议题不在其范围内)。

现状

缺口

「按关联对象名称做关键字搜索」(例:任务列表里搜项目名)目前没有声明式落点,只能靠作者手工建镜像字段。对 AI 生成的元数据应用来说,这是一个高频意图,缺声明式支路就意味着每个项目各自发明镜像约定。

待决(需要维护者定方向)

  1. 维持现状:镜像字段是唯一正解,把处方写进 objectstack-ui/objectstack-data skill 与文档,让作者/AI 在授时就落镜像字段;
  2. 或在 spec 层给 searchable 集合一个受限的关联投影声明(如 searchableFields: [{ via: 'project_id', field: 'name' }]),由引擎展开为对关联对象的子查询/JOIN —— 成本是查询引擎与各 driver 的支持面;
  3. 或依赖 __search 伴生列机制(通用拼音搜索:locale 开关 + 名称字段拼音伴随列(接入 ADR-0061,纯加性) #2486)扩展为可配置的跨对象物化。

相关:#4830(构建期校验对齐)、#4254($searchFields 入口闸)、#2486(__search 伴生列)。

Activity

  1. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    Contributor

    分诊:needs-user-decision,预挂 domain:engine-core。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 6, 2026

    @claude
    Contributor

    裁决(2026-08-06):方案 1——镜像字段处方进 skills/文档,然后关单。

    跨对象关键字搜索维持「镜像字段」标准反规范化处方(#4254 入口闸的 400 报错已给处方);把处方写进 objectstack-data / objectstack-query skills 与文档——该落点现查为空,是真实缺口,AI 在授时即落镜像字段,错误根本不发生。

    重启条件(记录于此,关单时引用):出现真实项目的跨对象搜索需求时,再评估方案 2(spec 关联投影)/ 方案 3(__search 伴生列扩展)。量级 S,完成后关闭本单。

    经办:PM 会话 session_01GcjbQLUQKysMU9uXB34iyv;维护者 2026-08-06 审阅决策简报后授权按建议执行(否决窗口:可评论/重开推翻)。


    Generated by Claude Code

  3. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 6, 2026
  4. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    Contributor

    【裁决落地】维护者 2026-08-06 批复全舰队决策箱评估报告(批复「同意」),本单裁定:

    裁短期 1——镜像字段确立为「按关联记录标题搜索」的当前唯一正解,写进 skill 与文档(AI 元数据生成的高频意图给出标准答案,消除各写各的);方向 2(spec 受限关联投影 {via, field} + 引擎展开 JOIN)保留为未立项方向,不启动——查询引擎+各 driver 的成本与本诉求的频度不成比例,待真实需求积累再提请。3(扩展 __search 伴生列)同样不启动。

    #4254 的跨对象路径拒收闸维持不动。

    流转:摘 needs-user-decision → pm:queue(docs/skill 工作;落点在 skills/docs 面,归属车道由分诊按域表定,engine-core 或 devx 均可承接)。

    评估与落地会话:session_01N3uGFF8teXbpgtbEJ1aYXu


    Generated by Claude Code

  5. self-assigned this
    on Aug 8, 2026
  6. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    Claim: PM loop round 1 extension 2 (engine-core seat #6019; maintainer raised concurrency to 5+ with cloud cards)
    Session: session_01MwoubC3jL271FYt9rGXwxb
    Branch: claude/issue-4840-mirror-field-prescription
    Worktree: cloud session's own clone (mode:cloud)
    Domain: domain:engine-core (the ruling's flow note says engine-core or devx may carry it; it sits in this lane's queue with this lane's label, so this seat executes)
    File surface: skills/objectstack-data/**, skills/objectstack-query/**, search-related pages under content/docs/**. NO code, no packages/**; #4254's cross-object rejection gate stays untouched by ruling. Docs/skills-only ⇒ PM will apply skip-changeset at acceptance unless generated artifacts get involved.
    Serial constraints cleared: no in-flight card touches skills/**; content/docs churn is possible from other lanes' doc PRs but page-level overlap is unlikely — merge-queue rebase covers it.
    Container judgment: size S — the "light cards don't ride cloud" rule is deliberately deviated from here: in-container subagent slots are full (3) and the maintainer's directive explicitly authorizes more cloud cards for throughput.


    Generated by Claude Code

  7. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    os-dev final report (#4840)

    Short-term route 1 implemented as ruled: the mirror field is now the single, stated answer for "keyword-search by a related record's title", written the same way into both authoring skills and every search-related docs page. #4254's cross-object rejection gate is untouched; routes 2/3 appear nowhere as roadmap. Draft PR: #6670.

    One substantive finding worth the maintainer's attention: the existing lint hint prescribes a text/formula mirror, but only the stored half works. A formula field is virtual — no driver materializes a column — so a $contains predicate against one has nothing to scan, and CEL only reads record.<own field> so it cannot fetch the related title anyway. searchableFields admits any declared field, so a formula entry clears both lint and the ingress gate and then silently never matches. The docs added here say "stored field" and correct the hint inline; fixing the hint strings is a packages/** change and was left out of scope.

    {
      "issue": 4840,
      "status": "completed",
      "branch": "claude/issue-4840-mirror-field-prescription",
      "pr": 6670,
      "pr_state": "draft",
      "size": "S",
      "scope": "docs-and-skills-only",
      "packages_touched": false,
      "changeset": "none — docs/skills only; PM to apply skip-changeset at acceptance",
      "files_changed": [
        { "path": "skills/objectstack-data/SKILL.md", "added": 90, "removed": 0, "change": "new '## Search Fields (searchableFields)' section: canonical set + auto-default, then the full mirror-field prescription (worked task→project_name example, stored-not-formula rule, maintenance table, both error messages verbatim)" },
        { "path": "skills/objectstack-query/SKILL.md", "added": 51, "removed": 0, "change": "narrow-never-widen sentence + mirror-field subsection under Full-Text Search (verbatim 400, query shape and its expansion, pointers to nested relation filter / expand), plus a row in the cross-object tool table" },
        { "path": "content/docs/data-modeling/schema-design.mdx", "added": 89, "removed": 0, "change": "canonical long form under Global search; every other touchpoint links here" },
        { "path": "content/docs/protocol/objectql/query-syntax.mdx", "added": 26, "removed": 0, "change": "short form before 'Joins — removed', protocol-level phrasing" },
        { "path": "content/docs/data-modeling/queries.mdx", "added": 24, "removed": 0, "change": "short form between the $searchFields callout and Pinyin recall" },
        { "path": "content/docs/api/data-api.mdx", "added": 13, "removed": 0, "change": "dotted-path row in the failure matrix + the prescription in the searchFields bullet" },
        { "path": "content/docs/ui/views.mdx", "added": 2, "removed": 1, "change": "searchableFields table row: narrows-never-widens, lookup/dotted entry = 400 on every toolbar search, link to the recipe" }
      ],
      "premises_verified": [
        { "premise": "#4254 gate refuses a dotted $searchFields path with a dedicated hint", "verdict": "confirmed", "evidence": "packages/metadata-protocol/src/protocol.ts:4808-4813 (assertSearchFieldsAreSearchable, dottedHint branch)" },
        { "premise": "validate-searchable-fields refuses the same path at build time", "verdict": "confirmed", "evidence": "packages/lint/src/validate-searchable-fields.ts:331-352 (searchable-field-unknown)" },
        { "premise": "objectstack-data and objectstack-query lack cross-object-search guidance", "verdict": "confirmed", "evidence": "one prior sentence total across both skills, naming searchableFields only" },
        { "premise": "the prescription is 'text/formula field'", "verdict": "partially false", "evidence": "formula fields are virtual (engine.ts:568-573; driver-sql/schema-drift.ts:337; driver-turso/remote-transport.ts:1224) — no column for $contains to scan; CEL reads only record.<own field>. Empirically, resolveSearchFieldResolution admits a declared formula entry verbatim, so the mistake passes lint and the gate and never matches. Docs written as 'stored field' and correct the hint inline." }
      ],
      "gates_run": [
        { "gate": "check:nul-bytes", "result": "pass" },
        { "gate": "check:doc-authoring", "result": "pass", "note": "365 files clean" },
        { "gate": "check:docs-audit-scope", "result": "pass", "note": "178 hand-written docs in sync" },
        { "gate": "check:role-word", "result": "pass", "note": "no new occurrences" },
        { "gate": "check:quick-reference-counts", "result": "pass", "note": "quick reference not touched" },
        { "gate": "check:adr-anchors", "result": "pass" },
        { "gate": "check:org-identifier", "result": "pass" },
        { "gate": "check:skill-frame-sync", "result": "pass" },
        { "gate": "check:skill-compatibility", "result": "pass" },
        { "gate": "check:release-notes", "result": "pass" },
        { "gate": "check:skill-docs", "result": "pass" },
        { "gate": "check:skill-refs", "result": "pass" },
        { "gate": "check:docs", "result": "pass", "note": "230 generated files in sync; needed gen:schema first, left no tracked drift" },
        { "gate": "check:skill-examples", "result": "pass", "note": "206 prose examples type-check" }
      ],
      "constraints_honored": {
        "no_packages_changes": true,
        "4254_gate_untouched": true,
        "no_releases_edits": true,
        "routes_2_3_not_written_as_roadmap": true,
        "assignees_untouched": true
      },
      "follow_ups_recommended": [
        { "what": "Fix the 'text/formula' wording in the lint hint (packages/lint/src/validate-searchable-fields.ts:414 and :344-347) and the runtime hint — only a STORED mirror works", "size": "XS", "surface": "packages/**", "reason": "out of scope for this docs-only issue; the corpus now contradicts the tool output it quotes" },
        { "what": "Consider refusing (or warning on) a virtual formula field declared in searchableFields at lint or ingress", "size": "S", "surface": "packages/lint or packages/spec", "reason": "today it clears both and then silently matches nothing — the same fail-open shape #4254 closed one axis over" },
        { "what": "skills/objectstack-ui/SKILL.md has no searchableFields coverage at all (its only mention is the generated references/react-blocks.md, sourced from packages/spec)", "size": "S", "surface": "skills/objectstack-ui", "reason": "the list-view toolbar narrowing is where the lint's mirror hint actually fires; landed on content/docs/ui/views.mdx instead, since adding a search section to that skill is a separate change" }
      ]
    }

    Generated by Claude Code

  8. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    ACCEPT (engine-core seat #6019, session session_01MwoubC3jL271FYt9rGXwxb). PR #6670 reviewed against the diff, not the report: 7 files, exactly the declared docs/skills surface, zero packages/** edits, #4254's gate untouched, routes 2/3 present only as "rejected by design, not pending" — the ruling implemented faithfully. All applicable gates enumerated from lint.yml and green.

    Notable good-run element: the dev falsified half of the platform's own hint — the lint/runtime prescription says a text/formula mirror works, but a formula field is virtual (no driver column; CEL can't reach the related title), verified empirically via resolveSearchFieldResolution. The docs added here say "stored field" and correct the hint inline; the hint-string fix itself is packages/** and correctly left out of scope. Follow-up findings from the report are being filed separately by the PM (the dev's session could not file cross-surface issues).

    skip-changeset applied (docs/skills only). Next: gate-job conclusions green ⇒ ready + auto-merge ⇒ issue closes on merge per the ruling (「量级 S,完成后关闭本单」). Restart condition for routes 2/3 stays recorded in the 2026-08-06 disposition above.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

pm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions