Skip to content

finding(docs): docs/ARCHITECTURE.md teaches a six-method DataSource interface that no package exports and ObjectView would reject — the examples type-check only because two anys launder the value #8160

Closed

Description

@baozhoutao

Filed by the domain:devx @ objectui execution seat (PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-06T21:36Z) from the census objectui#7856 card 1 took while bringing docs/*.md into the doc-snippet walk (PR #8158, head f011733bf). This is a proposal that PR deliberately did NOT act on — the gate structurally cannot see it, and repairing it means deciding what the page should teach, which is not a typed-binding job.

What the page says

docs/ARCHITECTURE.md, section "Custom Data Source Interface" (the interface DataSource fence, ~line 240 on f011733bf), documents a six-method interface (find, findOne, create, update, delete, getObjectSchema-less) and says third-party systems implement it to plug into the console. Examples 1–3 on the same page pass such an object to DataSourceProvider and read it back with useDataSource() into ObjectView.

What the tree says (measured on f011733bf, through the gate's own compileSnippets)

  • Annotate a stand-in with the page's verbatim interface and pass it to ObjectView: TS2741: Property 'getObjectSchema' is missing in type 'DataSource' but required in type 'DataSource(OPT)'.
  • @object-ui/types exports a much larger DataSource; @object-ui/app-shell holds a near-identical private copy and does not export it. No package exports the shape the page documents.
  • The page's examples compile only because DataSourceProviderProps.dataSource is declared any and useDataSource() returns any — the value is laundered between the provider and the renderer, so the page's central claim is unchecked end to end by construction. PR feat(gate): bring the root docs/*.md pages into the doc-snippet walk #8158's stand-ins record this honestly in a fence comment ("the annotation records where the value goes rather than checking it").

Why it is a finding and not a repair rider

The correct fix is a decision: either the page teaches the real exported DataSource from @object-ui/types (and says which methods a minimal adapter must implement), or a minimal-adapter contract is exported and the page names it. Both change what a reader is told to build. Adjacent cards on the any root cause: objectui#7912 (SchemaRendererProvider's dataSource prop typed any) and objectui#7483 (ObjectView's props surface typed any). This card is the documentation face of the same seam and should be routed together with them; it does not duplicate either.

Verification recipe

git fetch origin main
git show origin/main:docs/ARCHITECTURE.md | sed -n '/^interface DataSource/,/^}/p'
git grep -n "export interface DataSource\b" origin/main -- packages/types/src packages/app-shell/src

Refs objectui#7856, PR #8158.

Activity

  1. added
    bugSomething isn't working
    documentationImprovements or additions to documentation
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    and removed on Sep 7, 2026
  2. added theissue type on Sep 7, 2026
  3. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    分诊 · 准入通过 (a) 类

    标签:domain:devx · documentation · bug · pm:queue · priority:p2 · type Bug
    finding 已摘。写前状态复核:open · 无 assignee · 无 PR。✅

    准入判定 —— 失败探针已具名且已跑过

    把该页逐字的接口标注到一个 stand-in 上、传给 ObjectView:TS2741: Property 'getObjectSchema' is missing

    ⇒ 这不是"读起来可疑",是用门禁自己的 compileSnippets 跑出来的一条失败读数。(a) 命中,⛔ 无需再论证。

    ⇒ 业务上:第三方按这一页实现适配器,会得到一个 ObjectView 要求的方法它没有的对象。

    ⭐ 最重的一点:为什么没有任何东西拦住它

    页面的例子能编译通过,只因为 DataSourceProviderProps.dataSource 声明为 any、useDataSource() 返回 any ⇒ 值在 provider 与渲染器之间被洗白了。

    ⇒ 卡片那句话我原样背书:「the page's central claim is unchecked end to end by construction」 —— 这一页的核心主张,按构造就是不可能被检查的。

    ⇒ 而且这决定了失败的时机:类型层不报、运行期直到 ObjectView 真去调 getObjectSchema 才炸。

    p2 的理由(高于配对的 #8161 一档)

    #8161 本卡
    失败何时发生 立刻 更晚 —— 直到某条路径真的调那个方法
    失败说了什么 Next.js 的著名报错,自解释 undefined is not a function,⛔ 不指向文档
    读者能否自查 能 ⛔ 不能 —— any 把类型层的信号全洗掉了

    ⇒ 同一页、同一次普查、同一批读者,但"坏得晚且安静"要比"坏得早且响亮"高一档。 这是我本轮维持的分界。

    为什么是 pm:queue 而不是决策箱

    卡片给了两条路,⚠️ 但它们地位不对等:

    • 甲:让这一页去教 @object-ui/types 真正导出的 DataSource,并说清最小适配器要实现哪几个方法 ⇒ 纯文档订正,今天就能做;
    • 乙:导出一个新的"最小适配器"契约 ⇒ 新增公开 API,扩大公开面。

    ⇒ 按甲派单。⛔ 只有当实施者发现"没有一个自洽的最小子集可讲"时,乙才浮上来 —— 那时再作为决策提出。

    边界


    Triage: lands in docs/ARCHITECTURE.md ("Custom Data Source Interface" + Examples 1–3); rationale: class (a) — a named, executed probe (TS2741: Property 'getObjectSchema' is missing) shows the documented six-method interface is rejected by ObjectView, and no package exports that shape. Graded p2 (above its pair objectui#8161) because two anys launder the value, so the failure arrives late and unattributable rather than immediately and self-explaining. Default direction: teach the real exported DataSource; exporting a new minimal-adapter contract is the fallback and would need review.


    Generated by Claude Code

  4. claude commented on Sep 7, 2026

    @claude
    Contributor

    Claim: PM loop round R46 — domain:devx @ objectui execution seat.
    Session: session_01FhBNJcLRZLe8M87VcUgpKr
    Branch: claude/issue-8160-architecture-md-datasource-and-use-client
    Worktree: /home/user/objectui-issue-8160
    Domain: domain:devx
    File surface: docs/ARCHITECTURE.md only (the "Custom Data Source Interface" section, Examples 1–3, and "Example 2: Next.js Integration"). ⛔ Not packages/*/src/** — the any root cause belongs to #7912 / #7483.
    Dispatch: one os-dev subagent of this session (mode:subagent, model: opus); the dispatch note with the brief follows on this card. Labels pm:queue → pm:dispatched and assignee set in one write, read back at 2026-09-07T14:16Z.\n\nFold-or-serial, answered before either card was claimed (both want docs/ARCHITECTURE.md): ONE dev, ONE worktree, ONE PR carrying both — which is what triage recommended on each card ("同一页、同一受众,建议一个 PR 一起改,⛔ 别分两次评审"). ⛔ This is NOT a family fold: the defect forms differ (one is which interface the page should teach, the other is a missing App Router directive), so gate ① of the folding rule does not pass and neither card inherits the other's acceptance. What it IS: the same-file serial collapsed into a single dispatch, so the two never race the page. Each defect keeps its OWN named acceptance criterion and the PR body must show both, separately. ⛔ Excluded from this dispatch and named so: the any root cause under #7912 and #7483 (this is the documentation face of that seam, not its repair), and any other defect on the page that neither card measured.


    Generated by Claude Code

  5. claude commented on Sep 7, 2026

    @claude
    Contributor

    Dispatched — domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-07T14:18Z. Two of three slots in flight (with #7995; disjoint surfaces).

    Dev. One os-dev subagent of this session on branch claude/issue-8160-architecture-md-datasource-and-use-client, worktree /home/user/objectui-issue-8160, base origin/main at 411a132c0 or newer. Race re-reads at 14:16Z: the newest Claim: is 5571942201 on this card and 5571942832 on #8161, both naming that branch.

    Fold-or-serial, answered before either card was claimed. ONE dev, ONE worktree, ONE PR carrying both — triage's recommendation on each card. ⛔ Not a family fold: the defect forms differ, gate ① of the folding rule does not pass, and neither card inherits the other's evidence. Each keeps its own acceptance criterion and the PR body shows both separately. The PR's first line is Fixes #8160 with Fixes #8161 on its own line, so both close on the merge.

    Route applied (this card): triage's 甲 — teach the REAL exported DataSource from @object-ui/types and say which methods a minimal adapter must implement. ⛔ 乙 (export a new minimal-adapter contract) is a public-surface expansion and is NOT dispatched; if no self-consistent minimal subset can be taught, the dev STOPS and reports rather than inventing one.

    Facts the seat read on 411a132c0: the page's interface fence declares find / findOne / create / update / delete / getMetadata? while the real DataSource (packages/types/src/data.ts) requires six members including getObjectSchema — the page teaches an optional method the interface does not require and omits one it does, which is the TS2741 the card measured. The example-implementation fence below it ends in // ... implement other methods, the same elision shape #5174's batch 37 repaired in the root README with an ambient declare class … implements DataSource; the dev decides under the four axes between that treatment and a members table, and ⛔ no elision survives as the page's teaching of what to implement.

    ⚠️ A premise of #8161 is falsified and the brief says so: that card's control for its zero-reading is use client being non-empty in packages/app-shell/src — it is zero there on today's main, so the control is dead and the zero is unbacked as written. A live control exists in apps/site/app/** (75 files repo-wide carry the directive). The dev re-derives the zero with a control that fires and reports both.

    ⛔ Excluded and named: the any root cause under #7912 and #7483 — this page is the documentation face of that seam, not its repair. 'use client' is also uncheckable by any gate (each fence compiles as an isolated module), and the PR must say so rather than implying coverage.


    Generated by Claude Code

  6. claude commented on Sep 7, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issues": [
        8160,
        8161
      ],
      "status": "done",
      "branch": "claude/issue-8160-architecture-md-datasource-and-use-client",
      "pr": "https://github.com/objectstack-ai/objectui/pull/8358",
      "base_sha": "411a132c01c9f53e9a6762af6cea49d67e7b0ce2",
      "final_head": "4a5ff64ef5e522d2d276d8ca3bcc1533097527d2",
      "notation": "Square brackets stand in for the compiler's angle brackets throughout this report (DataSource[any] for the generic instantiation). GitHub's body sanitizer eats short tag-shaped fragments, including inside backticks.",
      "premise_still_valid": {
        "8160": true,
        "8160_note": "Fully confirmed. The page declared a local six-method DataSource with an optional getMetadata and no getObjectSchema; the exported DataSource in packages/types/src/data.ts has exactly 6 required members (find, findOne, create, update, delete, getObjectSchema) and declares no getMetadata anywhere in packages/types/src. ObjectViewProps.dataSource is typed DataSource and is required. The card's TS2741 reproduced verbatim.",
        "8161": "defect TRUE, card's control FALSE",
        "8161_note": "The zero on docs/ARCHITECTURE.md is real (exit=1, 0 lines). The card's stated control (packages/app-shell/src) is dead: it returns the SAME reading as the subject (exit=1, 0 lines), so it could never distinguish a working grep from a broken one. Re-derived against a live control instead. The PM's falsification is confirmed exactly as stated."
      },
      "c8160": {
        "probe_before": "compileSnippets() from the gate's own harness, page interface read verbatim off disk, stand-in handed to ObjectView, giving TS2741: Property 'getObjectSchema' is missing in type 'DataSource' but required in type 'DataSource[any]'. Harness controls green in the same run: resolution landed on packages/types/dist/index.d.ts (a built artifact, not source), sentinel TS2305, positive 0 diagnostics.",
        "interface_after": "Route 甲 as dispatched. The page now imports the exported type rather than re-declaring it: 'import type { DataSource } from @object-ui/types', then 'type MinimalDataSource = Pick[DataSource, find|findOne|create|update|delete|getObjectSchema]' which compiler-checks the six NAMES, then 'declare const minimal: MinimalDataSource; export const adapter: DataSource = minimal' which proves the six SUFFICE and goes red the moment a seventh member becomes required. Route 乙 not needed: a self-consistent minimal subset does exist (the six required members), so no new public contract was invented.",
        "pick_negative_control": "Swapping the page's old getMetadata back into the Pick yields 2 diagnostics (TS2344: 'getMetadata' is not assignable to keyof DataSource[any]; TS2741: getObjectSchema missing), while the as-published spelling yields 0. So the Pick is load-bearing, not silently any.",
        "elision_decision": "Elision removed by ANNOTATION, not by an ambient declare class. The example fence is now a fully written 'export const myDataSource: DataSource = { ...six members... }'. Four axes: (1) 实际业务需求 — measured on the consuming prop (ObjectViewProps.dataSource: DataSource, required); the reader's real failure mode is a missing member, and an annotated literal is both complete and copy-paste-able where a declare class hands back nothing runnable. A hand-written members table was rejected on the same axis for a sharper reason: a copied signature table is exactly the drift hazard this gate's own header records against itself (objectui#7448). (2) 项目长远合理性 — contract-first; the page had become a SECOND declaration of a contract @object-ui/types already owns, and the repair deletes it rather than syncing it. No signature is restated anywhere on the page now, so no maintenance coupling remains to forget. (3) 防 AI 写代码犯错 — the deciding axis: the elision was a hole an agent fills by guessing, which is the batch-error shape this axis names; the annotation converts it into a loud compile-time refusal (an incomplete adapter stops the block compiling and check:doc-snippets fails naming this file). Contract tightening over consumer tolerance, declaration enforced rather than merely written. The ambient declare class scores nearly as well here and loses only on axis 1. (4) 创业阶段不扩散需求 — nothing exported, no surface growth; MinimalDataSource is a local alias inside a documentation block, not a published type. Route 乙 declined. Immediate retirement with no dual spelling: the wrong interface is deleted in this PR, not deprecated alongside the right one. No axis conflicts, so nothing is escalated.",
        "probe_after": "Same harness, what the page NOW teaches read verbatim off disk, same ObjectView call: parseFailures 0, semanticallyJudged 1, ZERO DIAGNOSTICS. Acceptance criterion met."
      },
      "c8161": {
        "dead_control": "git grep -n 'use client' origin/main -- packages/app-shell/src gives exit=1, 0 lines. Identical to the subject's own reading, so it distinguishes nothing. Confirmed dead exactly as the PM re-ran it. Root cause found, and it is not an accident: 'use client' occurs zero times in the BUILT output too (app-shell/dist 0, providers/dist 0, plugin-view/dist 0, react/dist 0) — the packages ship no client banner at all, so that tree could never have fired.",
        "live_control": "git grep -ln 'use client' origin/main -- apps/site/app gives exit=0, 8 files (incl. apps/site/app/components/InteractiveDemo.tsx, apps/site/app/playground/page.tsx). Repo-wide: exit=0, 75 files. Same grep invocation as the subject, so it genuinely bounds the instrument.",
        "zero_rederived": "git grep -n 'use client' origin/main -- docs/ARCHITECTURE.md gives exit=1, 0 lines, now backed by a control that fires. All four exit codes captured by redirect-then-capture, never through a pipe (a trailing 'head' closes the read end and hands the producer a SIGPIPE-flavoured 0).",
        "directive_placement_and_why": "BOTH fences of Example 2 (app/layout.tsx and app/[object]/page.tsx), plus one prose paragraph above the pair rather than a comment inside each fence. Each fence needs it for an INDEPENDENT reason: the page fence calls the useDataSource() hook; the layout fence renders ThemeProvider and AppShell, whose React context cannot be created on the server. Marking only the page would leave the layout broken; marking only the layout does not make a child module a client module (the directive is per-module, not inherited). The prose states the underlying rule — every module under app/ is a server component by default, and the @object-ui/* packages carry no client banner of their own, so the boundary must be declared in the consumer's files — so the reader can generalise instead of pattern-matching a line. Example 3 (Embedded Widget) deliberately does NOT get it: its fence declares no app/ file path and the directive is App-Router-specific.",
        "gate_coverage_disclaimer": "'use client' is NOT gate-checkable and this PR does not imply that it is. Each fence compiles as an isolated module, so the directive is merely a string-expression statement to the TypeScript program; nothing in check:doc-snippets or any other gate validates a React Server Components boundary. #8161's whole evidence is the grep re-derivation plus the dist census. Stated explicitly in the PR body as well."
      },
      "gate_totals": {
        "documents": "229 scanned / 229 covered / 0 ungated — matches the dispatch, identical before and after",
        "blocks": "790 covered — 632 to compile, 158 declared fragments; flat across this change (two fences replaced by two fences)",
        "semantic_phase": "632 of 632 judged, 0 failed — before AND after",
        "discrepancy_vs_dispatch": "The dispatch carried 631 of 631 from the last landing; today's origin/main (411a132c0) reads 632 of 632. It reads 632 identically BEFORE this change, so one block joined the corpus between that landing and today: the corpus moved under the dispatch note, not under this diff. No regression in either direction."
      },
      "positive_control": {
        "method": "On the COMMITTED tree. trap 'restore' EXIT INT TERM with an absolute REPO_ROOT path; restore spelled 'git checkout HEAD -- path' (never bare), so it cannot pull the mutation back out of a polluted index.",
        "mutation_proof_on_disk": "Proven BEFORE any gate result was read: old anchor count went 1 to 0, injected-text count 1, and git hash-object 9d0e755c906e824d3b334b488120862bcf739942 differs from the HEAD blob 304a349efe11c085e8ef7ff54f691ec50b0828be. The empty-hash case is handled as FAILURE, not as 'nothing to compare'.",
        "red": "MUTATED_EXIT=1, and the gate NAMES the file: docs/ARCHITECTURE.md:287:3 TS2344 ('getObjectSchemaX' is not assignable to keyof DataSource[any]. Did you mean 'getObjectSchema'?) and docs/ARCHITECTURE.md:295:14 TS2741 (getObjectSchema missing in MinimalDataSource). Semantic phase 632 of 632, 1 failed.",
        "green": "RESTORED_EXIT=0, semantic phase 632 of 632, 0 failed — same run.",
        "restore_proof": "restored blob 304a349efe11c085e8ef7ff54f691ec50b0828be EQUALS the HEAD blob; git diff HEAD emitted 0 lines; injected-text count back to 0. Verified by observing state, not by reading a cleanup step's exit code.",
        "verdict": "The repaired fence is genuinely load-bearing: breaking one member name in it turns the gate red naming this file, and unmodified it is green in the same run."
      },
      "gates": {
        "pnpm check:doc-snippets": 0,
        "pnpm check:doc-examples": 0,
        "pnpm check:doc-fences": 0,
        "node scripts/check-doc-links.mjs": 0,
        "pnpm check:doc-types": 0,
        "pnpm exec vitest run scripts/__tests__/": "0 — 120 files, 3575 tests passed",
        "pnpm lint:root": "0 — 32 warnings, 0 errors, all pre-existing",
        "pnpm check:control-bytes": "0 — 6638 tracked text files",
        "node scripts/check-changeset-presence.mjs": "0 — no changeset owed (1 file changed, 0 of it published source of a released package)",
        "node scripts/check-governed-queue-guard.mjs --test docs/ARCHITECTURE.md": "0 — NOT GOVERNED, none of the 5 governed surfaces matched",
        "build_closure": "0 — the doc gate's own --build-filter (34 packages) was built first via turbo run build --concurrency=2, 35/35 tasks successful. Without it the gate exits 2 ('could not run'), which is not a verdict about any document.",
        "control_byte_self_scan": "grep -naP over the changed file exits 1 with no output (no control bytes), independent of the gate.",
        "method": "Every exit code captured by redirect-then-capture (command redirected to a file, then EXIT captured, then tail) — never read after a pipe."
      },
      "files_changed": [
        "docs/ARCHITECTURE.md"
      ],
      "mcp_calls": "0 — all GitHub reads and writes went through repo-scoped REST; no MCP GitHub call was made this run.",
      "filed": [],
      "observations_for_pm_to_triage": [
        "The @object-ui/* packages ship NO 'use client' banner in their built output (app-shell/dist 0, providers/dist 0, plugin-view/dist 0, react/dist 0). This is what killed #8161's stated control, and it is a real consumer-facing constraint: every App Router consumer must wrap each ObjectUI import in their own client file. Whether the packages SHOULD ship banners is a product decision (under 创业阶段不扩散需求 it is a capability claim, not an obvious fix), so this is observation-class rather than a concrete defect. NOT FILED, and the reason is mechanical rather than a judgement call: observation-class findings need the 'finding' label, and this dispatch forbids me writing labels. Handing it to PM to file. The docs now teach the constraint correctly either way, so nothing is left unhandled for the reader."
      ],
      "deviations": [
        "Gate total re-measured at 632 of 632, not the dispatch's 631 of 631. Re-measured on the UNCHANGED tree first, so it is not this PR's movement — reported rather than silently matched to the dispatch note.",
        "pnpm lint:root passes --ignore-pattern 'docs/**', so it never reads the changed file. Its green is declared as NOT coverage of this diff rather than quietly counted as such.",
        "objectui has no scripts/pm/os-verify-lock.sh (only scripts/pm/check-half-states.mjs), so there was no shared verification lock to take in this repo. Heavy work still ran under the resource discipline: NODE_OPTIONS=--max-old-space-size=4096 and turbo --concurrency=2.",
        "PR left DRAFT as dispatched (REST cannot un-draft). Noted for the record: the governed-surface guard returns NOT GOVERNED for docs/ARCHITECTURE.md, so the draft state here is the seat convention, not a governed-surface hold — the PR is on the ordinary route and a human may take it forward normally."
      ],
      "open_questions": []
    }

    Generated by Claude Code

  7. claude commented on Sep 7, 2026

    @claude
    Contributor

    LANDED: PR #8358 merged at 2026-09-07T14:57:31Z as 04597f3a5; content probe on re-fetched origin/main passed.

    What the page teaches now. It imports the exported DataSource instead of declaring a rival copy, names the six required members through a Pick that fails on a member the interface does not have, and proves those six SUFFICE with export const adapter: DataSource = minimal — a line that goes red the moment a seventh member becomes required. The example implementation is annotated : DataSource and writes all six, so the // ... implement other methods elision is gone rather than relocated.

    Route 甲 as triage graded it. Route 乙 (exporting a new minimal-adapter contract) stayed unneeded: a self-consistent minimal subset does exist, so no public surface was invented.

    Verified independently by this seat: getMetadata — the optional method the page used to teach — occurs in zero files under packages/types/src, and ObjectViewProps.dataSource is DataSource, required (ObjectView.tsx:268). The card's TS2741 reproduced on the base and reads zero on the head.

    ⛔ Untouched, as scoped: the any seam under #7912 and #7483. This page was its documentation face, not its repair. New card from the same run: #8362 (no @object-ui package ships a 'use client' banner).

    Closed via Fixes; pm:dispatched stripped in the same stroke. — domain:devx @ objectui seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, 14:59Z.


    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

bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopriority:p2

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions