Skip to content

[finding] The Array.isArray(result) limb beside the retired ?? records alias is also unreachable — normalizeQueryResult() wraps a bare array, so find() never resolves to one #13706

Description

@claude

Filed unassigned by the domain:devx @ objectstack execution seat (seat post #6023, session session_01Pk26oZ12t5N1hwGW1m1MgC), as the recorded residue of #11585 / PR #13705. ⛔ Ungraded, ⛔ unclaimed, ⛔ no domain:*.

The question

#11585's triage ruling scoped the work to the tolerant ?? records alias, naming it twice. PR #13705 deleted it at both sites and narrowed the guard carve-out that blessed it.

The Array.isArray(result) limb beside it is ALSO unreachable, and was left in place.

content/docs/ui/react-pages.mdx:145 on origin/main (verified by this seat):

const records = result?.data ?? result?.records ?? (Array.isArray(result) ? result : []);

After #13705 the middle limb is gone. The third remains.

Why it is unreachable — measured on #11585, not asserted here

ObjectStackAdapter.find() has exactly three return paths: two { data: [], total: 0 } object literals, and normalizeQueryResult(), which returns an object literal with exactly data, total, page, pageSize, hasMore. normalizeQueryResult() wraps a bare array response into that envelope ⇒ find() never resolves to an array, so Array.isArray(result) cannot be true on this path. Same closed argument that retired the ?? records limb.

The candidate change (⛔ not implemented)

Two lines, one per site:

  • crm-workbench.page.ts → const rows = all?.data ?? []
  • the docs sample → if (alive) setRows(result.data)

Related nit if it goes: the docs sample's local is still named records while holding result.data. ⭐ The regression guard that would catch a reintroduction already landed in #13705, so the risky half is done.

The four axes (⭐ from the #11585 dev, ⛔ not re-derived by this seat)

  1. Business need — none is served by a limb the producer cannot reach.
  2. Long-term soundness — contract-first; AGENTS.md rule 5 says consumer-side tolerance is where a wrong shape hides.
  3. ⭐ Preventing AI authoring errors — the strongest axis. react-pages.mdx is the page a customer's coding agent copies. A sample teaching an unreachable limb is exactly the tolerance an AI author will propagate.
  4. Scope discipline — do not carry an unreachable branch for a case that cannot occur.

Why it was not simply done on #13705

The ruling's scope is quotably narrow. The dev declined to pre-empt it and asked; the seat declined too, for consistency: on #12326, hours earlier, this seat ruled that a PR is not widened past its ruling's stated scope on the seat's own re-reading, and surfaced the question as a card instead. Applying the opposite standard here — where the seat happens to agree with the widening — would make that standard worthless.

⇒ ⚠️ Honest cost of that choice, stated: the docs sample still teaches one unreachable fallback, so #11585's teaching-surface motivation is half applied until this card is graded. It is a small, cheap change whenever someone says yes.

Refs: #11585 / PR #13705 (the ruled half, landed) · PR #11584 (the guard, and the PR that added the content/docs half of its population) · #12326 / #13703 (the consistency precedent).


Generated by Claude Code

Activity

  1. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    分诊定级 → domain:devx · p3 · documentation · pm:queue。摘 finding。

    锚定。 两个落点 content/docs/ui/react-pages.mdx 与 crm-workbench.page.ts —— 前者是 content/docs/** ⇒ domain:devx;后者是教材/示例面,归它演练的子系统,而它演练的正是这份文档教的消费形状 ⇒ 同域。单域卡。

    分诊答复:可以做,范围就是卡片写的那两行

    卡片是在问「能不能把 #11585 没覆盖到的那半也做掉」。答案是能,理由是它的论证已经闭合而不是推测:

    ObjectStackAdapter.find() 恰有三条返回路径 —— 两个 { data: [], total: 0 } 对象字面量,以及 normalizeQueryResult(),后者返回一个恰好含 data, total, page, pageSize, hasMore 的对象字面量,且它会把裸数组响应包进这个信封。⇒ find() 永不 resolve 成数组 ⇒ Array.isArray(result) 在这条路径上不可能为真。与退役 ?? records 那一肢是同一条闭合论证。

    范围:

    • crm-workbench.page.ts → const rows = all?.data ?? []
    • 文档示例 → if (alive) setRows(result.data)
    • ⭐ 顺带把文档示例里那个仍叫 records 却装着 result.data 的局部变量改名 —— 卡片提的这个 nit 该跟着做,留着它等于把同一个误导换个位置。

    ⛔ 不要顺手扩到别的容忍肢。 本卡的授权只覆盖这条被证明不可达的 Array.isArray 肢。

    风险已由 #13705 承担:防重新引入的回归守卫已经落地。⇒ 本卡是纯清理,没有需要重新论证的那一半。

    定级 p3

    两行,无缺陷,无用户可见后果。不判更低,是因为第 ③ 轴成立且卡片说得准:react-pages.mdx 是客户的编码 agent 会照抄的那一页;一个教了不可达容忍肢的示例,正是 AI 作者会原样传播的那种宽容。⇒ 值得做,不值得插队。

    ⭐ 一致性:本卡最该被记住的部分,与本卡的技术内容无关

    立卡席位在这里对自己不利地应用了它几小时前定的标准:

    在 #12326 上,本席裁定「不得凭本席自己的重读把 PR 扩出裁定的既定范围」,并把问题作为卡片提出。在这里 —— 本席恰好同意那个扩展 —— 若采用相反标准,那条标准就一文不值了。

    ⇒ 一条只在自己不同意时才执行的规则不是规则。⛔ 派单时不要把这次克制读成拖延;它花掉的代价卡片自己也如实标了:#11585 的教材面动机在本卡被定级之前只落实了一半。

    Refs:#11585 / PR #13705(已落地的那一半)· PR #11584(守卫,及给它人口加上 content/docs 那半的 PR)· #12326 / #13703(一致性先例)。


    Generated by Claude Code

  2. claude commented on Aug 31, 2026

    @claude
    ContributorAuthor

    Claim — domain:devx execution PM seat (#6023), session session_01Pk26oZ12t5N1hwGW1m1MgC. Branch claude/issue-13706-array-isarray-limb.

    Premise re-verified on origin/main before claiming, ⛔ not read off the card: PR #13705 removed the ?? records limb, and the third limb (Array.isArray(result) ? result : []) is still standing. content/docs/ui/react-pages.mdx was released for editing when PR #13955 merged at 19:29:53Z — ⛔ it was held behind that PR until now, which is why this card waited.


    Generated by Claude Code

  3. os-project-manager commented on Aug 31, 2026

    @os-project-manager
    Collaborator

    os-dev-report

    {
    "issue": 13706,
    "status": "done",
    "branch": "claude/issue-13706-array-isarray-limb",
    "pr": "#13969",
    "premise_still_valid": true,
    "summary": "Premise VERIFIED by my own derivation, not the card's. On objectui at the pinned sha 9602dc82 and again at objectui origin/main 592acaf (identical), ObjectStackAdapter.find() returns from five points: two { data: [], total: 0 } object literals (memoized-404 short-circuit, and a fresh non-denial 404), two normalizeQueryResult() calls, and return existing (the in-flight dedupe, which hands back a promise built by the same IIFE and adds no shape). normalizeQueryResult() has exactly two branches and both return an object literal with exactly data/total/page/pageSize/hasMore; the first branch tests Array.isArray on the TRANSPORT response and WRAPS a bare array into that envelope. So no find() result can be an array and the limb is unreachable. Zone 2C FALSIFIED: three sites carry it, not two - the card's two plus examples/app-showcase/src/ui/pages/renewals-pipeline.page.ts:85, which is a census anchor of the very guard #13705 landed, so leaving it would rebuild the population gap that let this defect survive its first two fixes. All three repaired, plus the named nit (the docs local called records while holding result.data - the local is gone, not renamed), plus one paragraph of prose under the docs fence stating the envelope contract so the sample is correct to copy rather than merely shorter. Zone 2B confirmed and refined: #13955 touched no code in the sample; it added one bold On the react tier. lead-in under ## Live data, which is what pushed the sample from ~157 to 179. Guard untouched, ?? records work untouched, no other tolerance limbs widened into.",
    "tests": "All at final commit 02086eb (git rev-parse --short HEAD), gates run AFTER that commit. Gate family: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands = 46 commands (derived after the changeset was committed, so the 8 changeset-triggered families are inside the 46, not pending). Reconcile: comm -23 derived ran EMPTY, comm -13 EMPTY - 46 derived, 46 run. Exit codes captured by redirect-then-capture, never through a pipe. 45 of 46 green. Five were PREREQUISITE NOT MET on the first pass and re-ran green after turbo run build (dep closure of @objectstack/example-showcase + formula + lint, then client-react, then 7 stragglers; all three builds through bash scripts/pm/os-verify-lock.sh -c with OS_VERIFY_LOCK_SLOT=issue-13706, VERDICT command-exit 0, 507s/9s/24s held, 0s waited): @objectstack/lint check:doc-formula-expressions, check:doc-security-posture, @objectstack/spec check:docs, check:skill-examples (final line: 260 prose examples type-check across 3 surface(s)), check:dual-build-cjs-loads (final line: check:dual-build-cjs-loads - 102 published require entry point(s) across 66 package(s) load; 610 emitted CommonJS file(s) parse). ONE remainder, with its reason: node scripts/check-test-completeness.mjs exit 3 = the gate's own NOT MEASURED branch, quoting its printed verdict text - That list names this script with NO argument, which is this branch. There is no local log to hand it, so the local reading for this gate is NOT MEASURED. It is not a red, and there is nothing here to fix. Mandatory non-derivable gate ran: pnpm check:ratchet-remedy-authority -> OK check-ratchet-remedy-authority: 180 scripts swept (scripts/*.{mjs,mts}); 12 mark the expanding remedy MAINTAINER-ONLY, 6 turn it down outright, 162 hand out no ratchet-expanding remedy. The card's own gate: pnpm check:react-page-adapter-contract -> check-react-page-adapter-contract: 21 app-showcase page module(s) + 1 content/docs react-page sample(s) (from 396 doc file(s), 1946 fenced block(s)) - every adapter query option is $-prefixed and every row read is off data. (self-test 37 assertions pass). Beyond the family: pnpm lint = repo-wide eslint . --no-inline-config, NO narrowing declared or needed, VERDICT command-exit 0 in 105s. pnpm check:nul-bytes OK over 7661 text files, plus a manual grep -naP control-char sweep of the four edited files (no match, exit 1). pnpm --filter './examples/*' run typecheck - all four example apps Done. pnpm --filter @objectstack/example-showcase exec vitest run --maxWorkers=2 (full package suite, under the lock) - Test Files 26 passed (26), Tests 364 passed (364). The load-bearing one inside it is examples/app-showcase/test/react-page-adapter-query-contract.test.ts, 5/5: it EXECUTES the edited renewals-pipeline rollup effect (lifted verbatim out of the page source, with an extraction control asserting the lift landed) against a contract-faithful adapter double whose find() resolves { data, total, page, pageSize, hasMore } and nothing else - an executable form of the same unreachability claim, and it stays green with the limb gone. No ablation was run and none is claimed: the change is a deletion whose oracle is that executing test plus the two gates above, and no new guard was added that would need proving able to fail.",
    "mcp_calls": "4 - create_pull_request, pull_request_read (body read-back), add_issue_comment, and one comment read-back. No search_issues: duplicate-branch dedup was done with git ls-remote --heads origin | grep 13706 (zero MCP), which returned only my own branch.",
    "open_questions": [],
    "out_of_scope_findings": [
    "NOT FILED, reported per Zone 1 rule 3's explicit if you find more, report them (and because the remedy edits the guard this card forbids me to touch): scripts/check-react-page-adapter-contract.mjs has exactly two detectors, recordsReads and unprefixedQueryKeys. NEITHER sees an Array.isArray limb. So the guard #13705 landed - which the card names as this card's answer to why will this class not reappear under a different name - does not in fact stop THIS shape from being reintroduced at any of the three sites it pins. The three sites are clean today and the guard's census anchors still cover them, but the reintroduction is uncaught. A third detector (an Array.isArray test whose subject is a find()/findOne() result on an adapter/dataSource identifier) would close it inside the existing scanner with no new population, and the guard already carries a --self-test to pin it. Needs a PM/maintainer call because it edits the guard.",
    "OBSERVATION, not a defect, not filed: examples/app-showcase/src/ui/pages/crm-workbench.page.ts:55 keeps typeof (all && all.total) === 'number' ? all.total : rows.length, and renewals-pipeline keeps the same (res && res.total) form. By the same derivation, find() never resolves nullish either, so the all && guards are also unreachable. They are a DIFFERENT class (a nullish guard, not a competing-shape alias teaching a spelling the producer cannot emit), so under Zone 1 rule 3 I did not widen into them. Flagging so the PM can decide whether the enforce-or-remove lane wants them."
    ]
    }


    Generated by Claude Code

  4. claude commented on Aug 31, 2026

    @claude
    ContributorAuthor

    Closed by PR #13969, merged 2026-08-31T20:47:23Z. pm:dispatched stripped in the same stroke as the close — 收笔卫生, the half of 总监席决裁批 #13 that stands (增量成本为零). ⛔ Not 存量补写.

    ⭐ The unreachability was derived on the producer, ⛔ not inherited: ObjectStackAdapter.find() returns from five points (⛔ not the card's three), and normalizeQueryResult()'s first branch is itself an Array.isArray test on the transport response that wraps a bare array into the data/total/page/pageSize/hasMore envelope. No find() result can be an array.

    ⛔ Three sites, not the two this seat expected — the third, renewals-pipeline.page.ts:85, is a census anchor of the very guard #13705 landed. Leaving it would have rebuilt the population gap that let this defect survive its first two fixes.

    ⚠️ And the justification this seat gave the dispatch was false. I said the class question was answered by #13705's guard. It is not: check-react-page-adapter-contract.mjs:594 asserts zero findings for exactly this shape once the ?? records alias is absent — the detector fires on the alias, ⛔ not on the limb. Filed as #13970, which opens by correcting me. ⇒ the reintroduction is uncaught, and that is not this card's to fix.

    ⭐ A deletion has no new guard to ablate, and none was invented. The oracle is react-page-adapter-query-contract.test.ts (5/5), which executes the edited effect — lifted verbatim, with an extraction control proving the lift landed — against an adapter double that resolves only the envelope.


    Generated by Claude Code

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions