Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(app-shell): ReportView 对 element binding 读 dataSource.resource 作 dataSource.object 的宽容别名 —— 契约里没有 resource 这个键 #5116

Closed

Description

@yinlianghui

发现于 #5098 的实施(cast 感知复测 resource 读取点时扫到)。半径外,未夹带进 PR #5114。Filed unassigned, not claiming.

现象

packages/app-shell/src/views/ReportView.tsx 两处把 resource 当作 object 的候补拼写读:

:171   const objName = liveReport?.objectName || liveReport?.dataSource?.object || liveReport?.dataSource?.resource;
:273   const resource = dataFetchSource.dataSource.object || dataFetchSource.dataSource.resource;
:275     console.warn('ReportView: dataSource missing object/resource property');

但 element binding 的契约里没有 resource 这个键。ElementDataSourceConfig(packages/core/src/data-scope/element-data-source.ts:75)只声明:

export interface ElementDataSourceConfig {
  object: string;
  view?: string;
  filter?: unknown;
  sort?: ElementDataSourceSort[];
  limit?: number;
}

而且该 binding 的判定谓词 isElementDataSourceConfig(同文件 :127-134)是按 object 判定的 —— typeof value.object === 'string' && value.object.length > 0。也就是说:一个只写了 resource 没写 object 的 binding,根本过不了 isElementDataSourceConfig,在走 useElementDataSource 的那条路上会被判为「不是 binding」。ReportView 这两处 || 限支因此在契约面上是够不着的 —— 除非有生产者绕过 binding 直接塞 resource,而我没找到这样的生产者。

resource 在本仓确实是个真键,但属于别的 schema:CRUDSchema(packages/types/src/crud.ts:434,type: 'crud')。两者不是同一个面。

为什么值得记一笔(而不是"小事")

这是典型的消费端宽容形状:契约声明 object,消费者额外接受一个未声明的拼写。按 contract-first,这类 || / ?? 别名正是 AI 生成的元数据把错误藏进去、再扩散的地方 —— 一份写了 resource 的报表元数据会在 ReportView 里"能跑",却在任何走 isElementDataSourceConfig 的其它渲染器上静默失效,两边行为不一致且都不报错。

console.warn 的措辞 'dataSource missing object/resource property' 还把这个非契约拼写写进了面向用户的诊断里,等于对外承诺它是合法的。

复核方式

sed -n '168,174p;270,278p' packages/app-shell/src/views/ReportView.tsx
sed -n '75,82p;127,134p' packages/core/src/data-scope/element-data-source.ts
grep -rn "dataSource\.resource" packages/*/src apps/*/src      # 仅 ReportView 两处

分级

打 finding,不入 pm:queue:按现有证据这是够不着的宽容限支 + 一句会误导的 warn 文案,今天没有用户撞得上的路径(没找到写 resource 的生产者)。但严重度我判得未必准 —— 若确有绕过 binding 的生产者路径,它就升级成"两个渲染器对同一份元数据给出不同答案",留给 PM 分诊时定级。

修法方向(不预设):删掉两处 || 限支并把 warn 文案改回只提 object;若确需兼容旧数据,应在生产者或 spec 侧正名,而不是在消费者加别名。

Activity

  1. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator

    Triage (first-touch, triage seat, session session_01HXzdkKx5WjwwCTT3c7U5kB): promoted finding → pm:queue, type Task — consumer-side tolerance of an undeclared spelling (AGENTS.md #0.1), unreachable through the contract per the card's measurement, plus a user-facing warn message that legitimizes the non-contract key.

    Scope: delete the two ?.resource limbs in ReportView.tsx (:171, :273) and reword the :275 warn to mention only object. Premise-first: re-verify on current main that no producer writes dataSource.resource (the card measured none; resource is a real key only on CRUDSchema, a different surface) — if one is found, stop and report instead of deleting, since that would upgrade this to a two-renderers-disagree defect needing re-triage.

    Size/model suggestion: S, mode:subagent.


    Generated by Claude Code

  2. claude commented on Aug 21, 2026

    @claude
    Contributor

    Claim: PM loop round 21
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-5116-reportview-resource-alias
    Worktree: objectui-issue-5116
    Domain: repo:objectui
    File surface: packages/app-shell/src/views/ReportView.tsx (:171, :273, :275) + its tests (stop on breach; explain in the report)
    Container & model: M, mode:subagent
    Clause-②: yes — deletes consumer-side tolerance of an undeclared key, narrowing what the renderer accepts. Same family and same direction as #5120/#5350/#5351, all landed this round.
    Serial constraints cleared: packages/app-shell/src/views/** is free — PR #5480 (#5418) is MERGED. ⚠️ #5203 is in flight in the SAME package but in layout/ and hooks/ only — ⛔ do not touch either.

    ⛔ Governed-surface check run before dispatch: the declared surface hits none of AGENTS.md / CLAUDE.md / .claude/** / docs/adr/** / skills/**. If the work turns out to need one, stop and report — published skills/** ownership is an open question with the maintainer and no seat may land it.


    Generated by Claude Code

  3. added 3 commits that reference this issue on Aug 21, 2026
    511d688
    c550ed1
    d8a61a5
  4. os-support-ai commented on Aug 21, 2026

    @os-support-ai
    Collaborator

    os-dev-report

    {
      "issue": 5116,
      "status": "done",
      "branch": "claude/issue-5116-reportview-resource-alias",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5488",
      "premise_still_valid": true,
      "summary": "Deleted both consumer-side `?.resource` limbs in ReportView.tsx (:171 availableFields, :273 fetch) and reworded the :275 warning to name only `object`. Premise re-verified on current main before deleting, as triage required: the producer census found ZERO sites writing `resource` onto a report `dataSource` in objectui or in the objectstack framework repo, with a counter-probe (same multiline regex, `object` instead) returning 182 + 15 hits to prove the zero is a reading and not a broken search; authored corpora (docs, skills/**, examples, fixtures, all json/yaml/md/mdx) were swept too and every authored `resource:` key resolves to a different surface (CRUDSchema.resource, the DataSource adapter's first parameter, LiveExportOptions.resource, useViewData options, evaluatePermission) — each inspected individually. `git log -S` traced the limb to the commit that first added data loading to this view: it was speculative in that very commit, no producer preceded it. Also confirmed the spec twin ElementDataSourceSchema is a STRICT object, so `resource` is rejected there rather than ignored — the tolerance was unreachable through the contract, exactly as the card measured. One in-hunk rename: the local `resource` variable at the fetch site became `objectName` (last in-code echo of the non-contract spelling on the line being fixed); named in the PR body. Scope held to 3 files; layout/ and hooks/ (#5203) untouched; no governed surface touched.",
      "tests": "Per-shape behaviour measured BY RENDERING, before and after, via a throwaway probe that reports rather than asserts (deleted after use). object-only: queries `acct`, 1 row, fields `industry` -> unchanged. resource-only: queries `acct`, 1 row, fields `industry`, no warning -> NOT queried, 0 rows, fallback fields, warns 'ReportView: dataSource missing object property' (this is the behaviour removed; it now fails loudly instead of silently rendering off-spec metadata). object+resource: queries `acct` -> unchanged (`resource` was already inert). neither: not queried + warns 'missing object/resource property' -> not queried + warns 'missing object property'. New pin packages/app-shell/src/views/ReportView.dataSourceObjectKey.test.tsx (5 tests) covers all four shapes plus a warn-wording assertion; the resource-only cases put a REAL object with real fields behind the spelling, so 'not queried' proves it was not read rather than that nothing was there. REVERSE-VERIFICATION, both legs run from the COMMITTED state with direction predicted first: leg 1 (restore origin/main ReportView.tsx) predicted RED 3 failed/2 passed on `resource only` + `neither` + `warning names object` — observed EXACTLY that (expected 'acct' to be null / missing object/resource property / expected '' to contain 'object'). Leg 2 (restore ONLY the two || limbs, keep the reworded warning) predicted RED 2 failed/3 passed with `neither` now PASSING — observed EXACTLY that, which separates the two halves of the fix and shows `neither` is the assertion pinning the :275 reword. ABLATION REBUILD: no build was needed or done, and that is a property of the resolution path, not an omission — the test imports the component through a RELATIVE SOURCE specifier (./ReportView) and the root vitest alias table maps every @object-ui/* specifier to src/, so no dist/ is in the path; demonstrated rather than assumed, since the source-only edit moved the suite red and back green with no rebuild between. Restored and proven clean by BLOB HASH (worktree 13466eff4fc346c70267b5c2f44a3a9c910062b1 == committed blob), git status empty. GATE UNION RUN AFTER THE FINAL COMMIT, AT d8a61a5ce: vitest packages/app-shell/src/views/ = 285 files / 2817 passed / 1 skipped (pre-existing spec-version skipIf in flow-node-config.spec-reconciliation.test.ts, untouched, not mine); vitest packages/app-shell/src/console/ + src/__tests__/ = 59 files / 500 passed (the route + barrel consuming ReportView); new pin 5/5; `pnpm --filter @object-ui/app-shell type-check` clean with the script name echoed in the output (3 real type errors in the new test were found and FIXED, not suppressed — an untyped vi.fn gave mock.calls an empty tuple type; the spy now declares the real DataSource.find signature); eslint on both changed files 0 errors, and ReportView.tsx warning count measured 24 before / 24 after against an origin/main copy, so zero introduced; check:control-bytes, check:esm-specifiers, check:self-import, check:phantom-deps, check:doc-types all pass; check:doc-snippets passes after building the packages it resolves against, with its own sentinel/positive controls green (a real reading, not a skipped gate); check:eager-closure RATCHET passes at 3792.5 KB gzipped vs 3867.2 KB budget, 74.7 KB headroom, which required building apps/console and its closure. check-changeset-presence and check-changeset-no-major pass. Changeset added as `minor` (never `major` per AGENTS.md — it would drag the 39-package fixed group off the @objectstack major). No test skipped, disabled, or quarantined. Remote CI was still converging at report time (mergeable_state unstable), which is PM's leg per the dispatch contract.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. added a commit that references this issue on Aug 23, 2026
    60d452e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions