Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them - #13705

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases
Aug 31, 2026
Merged

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them#13705
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#11585

ObjectStackAdapter.find() resolves a normalized QueryResult that declares data and never records, so the ?? .records limb in both surviving repairs is unreachable by contract. This is behaviour-preserving.data is read first and always wins today, and both pages render correctly either way. What goes is a spelling the producer cannot emit, sitting in the page a customer copies from.

Three things ship, because the first two alone let the pattern come back green.

1 + 2 — the two tolerant aliases

  • examples/app-showcase/src/ui/pages/crm-workbench.page.ts:49(all && (all.data || all.records)) || [] becomes (all && all.data) || [].
  • content/docs/ui/react-pages.mdx:145result?.data ?? result?.records ?? ... loses the .records limb. This is the one that actively TEACHES the fallback.

The prose three lines above the app-showcase call said "Read .data first, with .records/array fallbacks for robustness". That sentence was the instruction the alias implemented, so it is rewritten to state the contract instead. The historical explanation of why .records was wrong is kept.

3 — the carve-out that blessed them

recordsOnlyReads() skipped any line carrying .data:

if(!trimmed.includes('.records'))continue;if(trimmed.includes('.data'))continue;// THIS ONE

That made data ?? records the one shape the detector could not see — which is precisely the shape both surviving repairs had landed as. The carve-out is replaced by comment and string-literal stripping (codeOnly()), so .records is judged as a property READ rather than as text that merely spells it. The detector is renamed recordsReads, since "only" no longer describes it.

Blanking string bodies is what lets the carve-out go safely: the webhook shape emit({ type: 'data.records.updated' }) is kept out of the sweep by the SELECTOR today, and a detector that is quiet only because of the selector is one population change away from firing.

Measured, not assumed

A .records PRODUCER search — none exists on this path.useAdapter() is typed ObjectStackAdapter | null (objectui packages/react/src/context/AppShellContext.tsx:10), so no other DataSource reaches a react page through it. ObjectStackAdapter.find() has exactly three return paths (objectui packages/data-objectstack/src/index.ts): two { data: [], total: 0 } literals, and normalizeQueryResult() at line 3461, which CONSUMES the transport envelope's records/value and returns an object literal with exactly data, total, page, pageSize, hasMore. records is a key it reads, never one it writes. The app-showcase's own contract-faithful adapter double returns the same five keys. The other .records shapes in the tree are different contracts and unreachable from these two sites: the @objectstack/clientPaginatedResult the adapter normalizes, the ObjectQL engine list shape in examples/app-showcase/src/automation/jobs/sweep-project-health.ts:107, ApiDataSource (which also extracts records INTO data), and the seed-authoring records: key in src/data/seed/index.ts. So the removal is behaviour-preserving at runtime, not just by declaration.

Guard population, before and after — zero bystanders. The sweep's population is unchanged: 21 app-showcase page modules + 1 content/docs react-page sample, from 395 doc files and 1946 fenced blocks, both before and after. Measuring the carve-out's load-bearing set over the guard's OWN population (importing collectSources, not re-guessing it) found it suppressing exactly two lines — the two deleted here — and nothing else.

Reverse-verified from the committed state: with the guard narrowed and both alias lines restored from the merge base, the gate exits 1 with exactly 2 findings, naming crm-workbench.page.ts:49 and react-pages.mdx:145 and no third file. The restore leg was proven byte-for-byte against the HEAD blobs. So the narrowing bites on the pattern and reds no unrelated existing code.

The self-test grows from 30 to 37 assertions: both aliases pinned as findings, the repaired docs sample pinned silent, and the over-fire directions pinned too — a string-literal data.records.updated, a trailing comment naming .records beside a canonical read, and .recordsCount.

Verification

Local gate union at 69e868cf9, the final commit. All 43 derived families green plus both convention-triggered gate-script obligations (bare-root-worklist --self-test; check:pm-dispatch-gates, 1017 cases). pnpm lint — the full-repo eslint . --no-inline-config — clean, run whole rather than narrowed. @objectstack/example-showcase: 26 files / 364 tests pass, typecheck clean, and test/react-page-adapter-query-contract.test.ts confirmed to have actually executed (5 tests) rather than inferred from a green suite.

Two families read NOT MEASURED rather than green: check-test-completeness and check:dual-build-cjs-loads both exit 3 PREREQUISITE NOT MET (a saved turbo test log; a full pnpm build). Neither is a finding.

One red was found and fixed during this run, by the gate family rather than by review: the rewritten comment first used backtick-quoted text, and the page body lives inside a source: template literal, so the backticks terminated it early. check:logger-receiver-detach refused to scan the unparseable file. The comment now uses the quoting convention the rest of the file already used, and every gate was re-run afterwards on the final tree.

Notes for review

  • No changeset, skip-changeset applied. Nothing here publishes: @objectstack/example-showcase is private: true, and the other two paths are docs content and a root gate script. scripts/pr-labels.mjs:44 states the criterion as "the exemption for a PR that publishes nothing". Precedent on these same files: the gate-creating PR feat(tooling): sweep the react-page useAdapter() contracts over the docs corpus too #11584 and the previous repair of this exact docs line, 243218a36, both landed changeset-free. Happy to add one if this seat reads it differently.
  • The Array.isArray limb is deliberately left in both sites. It is also unreachable against the current contract — normalizeQueryResult wraps a bare array response — and the card raises it as an open question that the triage ruling did not answer. Deciding it here would have pre-empted that, so it is reported instead. See the report comment on the issue.
  • Hot-file boundary respected: docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers #12650's html-tier / kebab-case work in content/docs/ui/react-pages.mdx is untouched. This diff changes one line in that file.

Generated by Claude Code


Generated by Claude Code

…essed them
`ObjectStackAdapter.find()` resolves a normalized `QueryResult` whose sole
non-empty return path (`normalizeQueryResult`) builds an object literal with
exactly `data`, `total`, `page`, `pageSize`, `hasMore`. `records` is a key it
READS off the transport envelope, never one it writes -- so the `?? .records`
limb in both surviving repairs is unreachable by contract.
Behaviour-preserving: `.data` is read first and always wins today. What goes is
a spelling the producer cannot emit, sitting in the page a customer copies from.
The third piece is what stops it returning: `recordsOnlyReads()` skipped any
line carrying `.data`, which made `data ?? records` the one shape it could not
see. That carve-out is replaced by comment/string stripping, and the detector
renamed `recordsReads` since "only" no longer describes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 07:45
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 789aa68Aug 31, 2026
37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-11585-records-tolerant-aliases branch August 31, 2026 08:18
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
`@objectstack/docs` patch, following #13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and #13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
…etector (#13970)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (#11585 -> #13705 ->
#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…d()` sites (objectstack-ai#13969)
* Delete the unreachable Array.isArray limb at all three adapter.find sites
`ObjectStackAdapter.find()` cannot resolve to an array. Re-derived on the
pinned objectui sha (9602dc82) and on objectui `origin/main`: find() has two
object-literal returns (`{ data: [], total: 0 }` for a memoized 404 and for a
fresh non-denial 404), two `normalizeQueryResult(...)` returns, and an inflight
`return existing` that hands back a promise from that same set. Both of
`normalizeQueryResult`'s branches return an object literal with exactly
`data, total, page, pageSize, hasMore` -- the first one WRAPS a bare array
response into it. So no `Array.isArray(<find result>)` limb can ever be taken.
Behaviour-preserving, like the `?? records` deletion beside it: `.data` was
already read first and always won. What goes is a shape the producer cannot
emit, in the sample a customer (and a coding agent) copies from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
* Add the changeset for the docs sample change
`@objectstack/docs` patch, following objectstack-ai#13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and objectstack-ai#13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
---------
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…etector (objectstack-ai#13970) (objectstack-ai#13989)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (objectstack-ai#11585 -> objectstack-ai#13705 ->
objectstack-ai#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-objectstack-ai#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the surviving .records repairs are tolerant ?? records aliases, and the guard's .data-beside carve-out is what blesses them

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them - #13705

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases
Aug 31, 2026
Merged

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them#13705
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#11585

ObjectStackAdapter.find() resolves a normalized QueryResult that declares data and never records, so the ?? .records limb in both surviving repairs is unreachable by contract. This is behaviour-preserving.data is read first and always wins today, and both pages render correctly either way. What goes is a spelling the producer cannot emit, sitting in the page a customer copies from.

Three things ship, because the first two alone let the pattern come back green.

1 + 2 — the two tolerant aliases

  • examples/app-showcase/src/ui/pages/crm-workbench.page.ts:49(all && (all.data || all.records)) || [] becomes (all && all.data) || [].
  • content/docs/ui/react-pages.mdx:145result?.data ?? result?.records ?? ... loses the .records limb. This is the one that actively TEACHES the fallback.

The prose three lines above the app-showcase call said "Read .data first, with .records/array fallbacks for robustness". That sentence was the instruction the alias implemented, so it is rewritten to state the contract instead. The historical explanation of why .records was wrong is kept.

3 — the carve-out that blessed them

recordsOnlyReads() skipped any line carrying .data:

if(!trimmed.includes('.records'))continue;if(trimmed.includes('.data'))continue;// THIS ONE

That made data ?? records the one shape the detector could not see — which is precisely the shape both surviving repairs had landed as. The carve-out is replaced by comment and string-literal stripping (codeOnly()), so .records is judged as a property READ rather than as text that merely spells it. The detector is renamed recordsReads, since "only" no longer describes it.

Blanking string bodies is what lets the carve-out go safely: the webhook shape emit({ type: 'data.records.updated' }) is kept out of the sweep by the SELECTOR today, and a detector that is quiet only because of the selector is one population change away from firing.

Measured, not assumed

A .records PRODUCER search — none exists on this path.useAdapter() is typed ObjectStackAdapter | null (objectui packages/react/src/context/AppShellContext.tsx:10), so no other DataSource reaches a react page through it. ObjectStackAdapter.find() has exactly three return paths (objectui packages/data-objectstack/src/index.ts): two { data: [], total: 0 } literals, and normalizeQueryResult() at line 3461, which CONSUMES the transport envelope's records/value and returns an object literal with exactly data, total, page, pageSize, hasMore. records is a key it reads, never one it writes. The app-showcase's own contract-faithful adapter double returns the same five keys. The other .records shapes in the tree are different contracts and unreachable from these two sites: the @objectstack/clientPaginatedResult the adapter normalizes, the ObjectQL engine list shape in examples/app-showcase/src/automation/jobs/sweep-project-health.ts:107, ApiDataSource (which also extracts records INTO data), and the seed-authoring records: key in src/data/seed/index.ts. So the removal is behaviour-preserving at runtime, not just by declaration.

Guard population, before and after — zero bystanders. The sweep's population is unchanged: 21 app-showcase page modules + 1 content/docs react-page sample, from 395 doc files and 1946 fenced blocks, both before and after. Measuring the carve-out's load-bearing set over the guard's OWN population (importing collectSources, not re-guessing it) found it suppressing exactly two lines — the two deleted here — and nothing else.

Reverse-verified from the committed state: with the guard narrowed and both alias lines restored from the merge base, the gate exits 1 with exactly 2 findings, naming crm-workbench.page.ts:49 and react-pages.mdx:145 and no third file. The restore leg was proven byte-for-byte against the HEAD blobs. So the narrowing bites on the pattern and reds no unrelated existing code.

The self-test grows from 30 to 37 assertions: both aliases pinned as findings, the repaired docs sample pinned silent, and the over-fire directions pinned too — a string-literal data.records.updated, a trailing comment naming .records beside a canonical read, and .recordsCount.

Verification

Local gate union at 69e868cf9, the final commit. All 43 derived families green plus both convention-triggered gate-script obligations (bare-root-worklist --self-test; check:pm-dispatch-gates, 1017 cases). pnpm lint — the full-repo eslint . --no-inline-config — clean, run whole rather than narrowed. @objectstack/example-showcase: 26 files / 364 tests pass, typecheck clean, and test/react-page-adapter-query-contract.test.ts confirmed to have actually executed (5 tests) rather than inferred from a green suite.

Two families read NOT MEASURED rather than green: check-test-completeness and check:dual-build-cjs-loads both exit 3 PREREQUISITE NOT MET (a saved turbo test log; a full pnpm build). Neither is a finding.

One red was found and fixed during this run, by the gate family rather than by review: the rewritten comment first used backtick-quoted text, and the page body lives inside a source: template literal, so the backticks terminated it early. check:logger-receiver-detach refused to scan the unparseable file. The comment now uses the quoting convention the rest of the file already used, and every gate was re-run afterwards on the final tree.

Notes for review

  • No changeset, skip-changeset applied. Nothing here publishes: @objectstack/example-showcase is private: true, and the other two paths are docs content and a root gate script. scripts/pr-labels.mjs:44 states the criterion as "the exemption for a PR that publishes nothing". Precedent on these same files: the gate-creating PR feat(tooling): sweep the react-page useAdapter() contracts over the docs corpus too #11584 and the previous repair of this exact docs line, 243218a36, both landed changeset-free. Happy to add one if this seat reads it differently.
  • The Array.isArray limb is deliberately left in both sites. It is also unreachable against the current contract — normalizeQueryResult wraps a bare array response — and the card raises it as an open question that the triage ruling did not answer. Deciding it here would have pre-empted that, so it is reported instead. See the report comment on the issue.
  • Hot-file boundary respected: docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers #12650's html-tier / kebab-case work in content/docs/ui/react-pages.mdx is untouched. This diff changes one line in that file.

Generated by Claude Code


Generated by Claude Code

…essed them
`ObjectStackAdapter.find()` resolves a normalized `QueryResult` whose sole
non-empty return path (`normalizeQueryResult`) builds an object literal with
exactly `data`, `total`, `page`, `pageSize`, `hasMore`. `records` is a key it
READS off the transport envelope, never one it writes -- so the `?? .records`
limb in both surviving repairs is unreachable by contract.
Behaviour-preserving: `.data` is read first and always wins today. What goes is
a spelling the producer cannot emit, sitting in the page a customer copies from.
The third piece is what stops it returning: `recordsOnlyReads()` skipped any
line carrying `.data`, which made `data ?? records` the one shape it could not
see. That carve-out is replaced by comment/string stripping, and the detector
renamed `recordsReads` since "only" no longer describes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 07:45
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 789aa68Aug 31, 2026
37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-11585-records-tolerant-aliases branch August 31, 2026 08:18
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
`@objectstack/docs` patch, following #13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and #13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
…etector (#13970)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (#11585 -> #13705 ->
#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…d()` sites (objectstack-ai#13969)
* Delete the unreachable Array.isArray limb at all three adapter.find sites
`ObjectStackAdapter.find()` cannot resolve to an array. Re-derived on the
pinned objectui sha (9602dc82) and on objectui `origin/main`: find() has two
object-literal returns (`{ data: [], total: 0 }` for a memoized 404 and for a
fresh non-denial 404), two `normalizeQueryResult(...)` returns, and an inflight
`return existing` that hands back a promise from that same set. Both of
`normalizeQueryResult`'s branches return an object literal with exactly
`data, total, page, pageSize, hasMore` -- the first one WRAPS a bare array
response into it. So no `Array.isArray(<find result>)` limb can ever be taken.
Behaviour-preserving, like the `?? records` deletion beside it: `.data` was
already read first and always won. What goes is a shape the producer cannot
emit, in the sample a customer (and a coding agent) copies from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
* Add the changeset for the docs sample change
`@objectstack/docs` patch, following objectstack-ai#13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and objectstack-ai#13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
---------
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…etector (objectstack-ai#13970) (objectstack-ai#13989)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (objectstack-ai#11585 -> objectstack-ai#13705 ->
objectstack-ai#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-objectstack-ai#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the surviving .records repairs are tolerant ?? records aliases, and the guard's .data-beside carve-out is what blesses them

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them - #13705

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases
Aug 31, 2026
Merged

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them#13705
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#11585

ObjectStackAdapter.find() resolves a normalized QueryResult that declares data and never records, so the ?? .records limb in both surviving repairs is unreachable by contract. This is behaviour-preserving.data is read first and always wins today, and both pages render correctly either way. What goes is a spelling the producer cannot emit, sitting in the page a customer copies from.

Three things ship, because the first two alone let the pattern come back green.

1 + 2 — the two tolerant aliases

  • examples/app-showcase/src/ui/pages/crm-workbench.page.ts:49(all && (all.data || all.records)) || [] becomes (all && all.data) || [].
  • content/docs/ui/react-pages.mdx:145result?.data ?? result?.records ?? ... loses the .records limb. This is the one that actively TEACHES the fallback.

The prose three lines above the app-showcase call said "Read .data first, with .records/array fallbacks for robustness". That sentence was the instruction the alias implemented, so it is rewritten to state the contract instead. The historical explanation of why .records was wrong is kept.

3 — the carve-out that blessed them

recordsOnlyReads() skipped any line carrying .data:

if(!trimmed.includes('.records'))continue;if(trimmed.includes('.data'))continue;// THIS ONE

That made data ?? records the one shape the detector could not see — which is precisely the shape both surviving repairs had landed as. The carve-out is replaced by comment and string-literal stripping (codeOnly()), so .records is judged as a property READ rather than as text that merely spells it. The detector is renamed recordsReads, since "only" no longer describes it.

Blanking string bodies is what lets the carve-out go safely: the webhook shape emit({ type: 'data.records.updated' }) is kept out of the sweep by the SELECTOR today, and a detector that is quiet only because of the selector is one population change away from firing.

Measured, not assumed

A .records PRODUCER search — none exists on this path.useAdapter() is typed ObjectStackAdapter | null (objectui packages/react/src/context/AppShellContext.tsx:10), so no other DataSource reaches a react page through it. ObjectStackAdapter.find() has exactly three return paths (objectui packages/data-objectstack/src/index.ts): two { data: [], total: 0 } literals, and normalizeQueryResult() at line 3461, which CONSUMES the transport envelope's records/value and returns an object literal with exactly data, total, page, pageSize, hasMore. records is a key it reads, never one it writes. The app-showcase's own contract-faithful adapter double returns the same five keys. The other .records shapes in the tree are different contracts and unreachable from these two sites: the @objectstack/clientPaginatedResult the adapter normalizes, the ObjectQL engine list shape in examples/app-showcase/src/automation/jobs/sweep-project-health.ts:107, ApiDataSource (which also extracts records INTO data), and the seed-authoring records: key in src/data/seed/index.ts. So the removal is behaviour-preserving at runtime, not just by declaration.

Guard population, before and after — zero bystanders. The sweep's population is unchanged: 21 app-showcase page modules + 1 content/docs react-page sample, from 395 doc files and 1946 fenced blocks, both before and after. Measuring the carve-out's load-bearing set over the guard's OWN population (importing collectSources, not re-guessing it) found it suppressing exactly two lines — the two deleted here — and nothing else.

Reverse-verified from the committed state: with the guard narrowed and both alias lines restored from the merge base, the gate exits 1 with exactly 2 findings, naming crm-workbench.page.ts:49 and react-pages.mdx:145 and no third file. The restore leg was proven byte-for-byte against the HEAD blobs. So the narrowing bites on the pattern and reds no unrelated existing code.

The self-test grows from 30 to 37 assertions: both aliases pinned as findings, the repaired docs sample pinned silent, and the over-fire directions pinned too — a string-literal data.records.updated, a trailing comment naming .records beside a canonical read, and .recordsCount.

Verification

Local gate union at 69e868cf9, the final commit. All 43 derived families green plus both convention-triggered gate-script obligations (bare-root-worklist --self-test; check:pm-dispatch-gates, 1017 cases). pnpm lint — the full-repo eslint . --no-inline-config — clean, run whole rather than narrowed. @objectstack/example-showcase: 26 files / 364 tests pass, typecheck clean, and test/react-page-adapter-query-contract.test.ts confirmed to have actually executed (5 tests) rather than inferred from a green suite.

Two families read NOT MEASURED rather than green: check-test-completeness and check:dual-build-cjs-loads both exit 3 PREREQUISITE NOT MET (a saved turbo test log; a full pnpm build). Neither is a finding.

One red was found and fixed during this run, by the gate family rather than by review: the rewritten comment first used backtick-quoted text, and the page body lives inside a source: template literal, so the backticks terminated it early. check:logger-receiver-detach refused to scan the unparseable file. The comment now uses the quoting convention the rest of the file already used, and every gate was re-run afterwards on the final tree.

Notes for review

  • No changeset, skip-changeset applied. Nothing here publishes: @objectstack/example-showcase is private: true, and the other two paths are docs content and a root gate script. scripts/pr-labels.mjs:44 states the criterion as "the exemption for a PR that publishes nothing". Precedent on these same files: the gate-creating PR feat(tooling): sweep the react-page useAdapter() contracts over the docs corpus too #11584 and the previous repair of this exact docs line, 243218a36, both landed changeset-free. Happy to add one if this seat reads it differently.
  • The Array.isArray limb is deliberately left in both sites. It is also unreachable against the current contract — normalizeQueryResult wraps a bare array response — and the card raises it as an open question that the triage ruling did not answer. Deciding it here would have pre-empted that, so it is reported instead. See the report comment on the issue.
  • Hot-file boundary respected: docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers #12650's html-tier / kebab-case work in content/docs/ui/react-pages.mdx is untouched. This diff changes one line in that file.

Generated by Claude Code


Generated by Claude Code

…essed them
`ObjectStackAdapter.find()` resolves a normalized `QueryResult` whose sole
non-empty return path (`normalizeQueryResult`) builds an object literal with
exactly `data`, `total`, `page`, `pageSize`, `hasMore`. `records` is a key it
READS off the transport envelope, never one it writes -- so the `?? .records`
limb in both surviving repairs is unreachable by contract.
Behaviour-preserving: `.data` is read first and always wins today. What goes is
a spelling the producer cannot emit, sitting in the page a customer copies from.
The third piece is what stops it returning: `recordsOnlyReads()` skipped any
line carrying `.data`, which made `data ?? records` the one shape it could not
see. That carve-out is replaced by comment/string stripping, and the detector
renamed `recordsReads` since "only" no longer describes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 07:45
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 789aa68Aug 31, 2026
37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-11585-records-tolerant-aliases branch August 31, 2026 08:18
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
`@objectstack/docs` patch, following #13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and #13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
…etector (#13970)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (#11585 -> #13705 ->
#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…d()` sites (objectstack-ai#13969)
* Delete the unreachable Array.isArray limb at all three adapter.find sites
`ObjectStackAdapter.find()` cannot resolve to an array. Re-derived on the
pinned objectui sha (9602dc82) and on objectui `origin/main`: find() has two
object-literal returns (`{ data: [], total: 0 }` for a memoized 404 and for a
fresh non-denial 404), two `normalizeQueryResult(...)` returns, and an inflight
`return existing` that hands back a promise from that same set. Both of
`normalizeQueryResult`'s branches return an object literal with exactly
`data, total, page, pageSize, hasMore` -- the first one WRAPS a bare array
response into it. So no `Array.isArray(<find result>)` limb can ever be taken.
Behaviour-preserving, like the `?? records` deletion beside it: `.data` was
already read first and always won. What goes is a shape the producer cannot
emit, in the sample a customer (and a coding agent) copies from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
* Add the changeset for the docs sample change
`@objectstack/docs` patch, following objectstack-ai#13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and objectstack-ai#13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
---------
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…etector (objectstack-ai#13970) (objectstack-ai#13989)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (objectstack-ai#11585 -> objectstack-ai#13705 ->
objectstack-ai#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-objectstack-ai#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the surviving .records repairs are tolerant ?? records aliases, and the guard's .data-beside carve-out is what blesses them

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them - #13705

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases
Aug 31, 2026
Merged

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them#13705
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#11585

ObjectStackAdapter.find() resolves a normalized QueryResult that declares data and never records, so the ?? .records limb in both surviving repairs is unreachable by contract. This is behaviour-preserving.data is read first and always wins today, and both pages render correctly either way. What goes is a spelling the producer cannot emit, sitting in the page a customer copies from.

Three things ship, because the first two alone let the pattern come back green.

1 + 2 — the two tolerant aliases

  • examples/app-showcase/src/ui/pages/crm-workbench.page.ts:49(all && (all.data || all.records)) || [] becomes (all && all.data) || [].
  • content/docs/ui/react-pages.mdx:145result?.data ?? result?.records ?? ... loses the .records limb. This is the one that actively TEACHES the fallback.

The prose three lines above the app-showcase call said "Read .data first, with .records/array fallbacks for robustness". That sentence was the instruction the alias implemented, so it is rewritten to state the contract instead. The historical explanation of why .records was wrong is kept.

3 — the carve-out that blessed them

recordsOnlyReads() skipped any line carrying .data:

if(!trimmed.includes('.records'))continue;if(trimmed.includes('.data'))continue;// THIS ONE

That made data ?? records the one shape the detector could not see — which is precisely the shape both surviving repairs had landed as. The carve-out is replaced by comment and string-literal stripping (codeOnly()), so .records is judged as a property READ rather than as text that merely spells it. The detector is renamed recordsReads, since "only" no longer describes it.

Blanking string bodies is what lets the carve-out go safely: the webhook shape emit({ type: 'data.records.updated' }) is kept out of the sweep by the SELECTOR today, and a detector that is quiet only because of the selector is one population change away from firing.

Measured, not assumed

A .records PRODUCER search — none exists on this path.useAdapter() is typed ObjectStackAdapter | null (objectui packages/react/src/context/AppShellContext.tsx:10), so no other DataSource reaches a react page through it. ObjectStackAdapter.find() has exactly three return paths (objectui packages/data-objectstack/src/index.ts): two { data: [], total: 0 } literals, and normalizeQueryResult() at line 3461, which CONSUMES the transport envelope's records/value and returns an object literal with exactly data, total, page, pageSize, hasMore. records is a key it reads, never one it writes. The app-showcase's own contract-faithful adapter double returns the same five keys. The other .records shapes in the tree are different contracts and unreachable from these two sites: the @objectstack/clientPaginatedResult the adapter normalizes, the ObjectQL engine list shape in examples/app-showcase/src/automation/jobs/sweep-project-health.ts:107, ApiDataSource (which also extracts records INTO data), and the seed-authoring records: key in src/data/seed/index.ts. So the removal is behaviour-preserving at runtime, not just by declaration.

Guard population, before and after — zero bystanders. The sweep's population is unchanged: 21 app-showcase page modules + 1 content/docs react-page sample, from 395 doc files and 1946 fenced blocks, both before and after. Measuring the carve-out's load-bearing set over the guard's OWN population (importing collectSources, not re-guessing it) found it suppressing exactly two lines — the two deleted here — and nothing else.

Reverse-verified from the committed state: with the guard narrowed and both alias lines restored from the merge base, the gate exits 1 with exactly 2 findings, naming crm-workbench.page.ts:49 and react-pages.mdx:145 and no third file. The restore leg was proven byte-for-byte against the HEAD blobs. So the narrowing bites on the pattern and reds no unrelated existing code.

The self-test grows from 30 to 37 assertions: both aliases pinned as findings, the repaired docs sample pinned silent, and the over-fire directions pinned too — a string-literal data.records.updated, a trailing comment naming .records beside a canonical read, and .recordsCount.

Verification

Local gate union at 69e868cf9, the final commit. All 43 derived families green plus both convention-triggered gate-script obligations (bare-root-worklist --self-test; check:pm-dispatch-gates, 1017 cases). pnpm lint — the full-repo eslint . --no-inline-config — clean, run whole rather than narrowed. @objectstack/example-showcase: 26 files / 364 tests pass, typecheck clean, and test/react-page-adapter-query-contract.test.ts confirmed to have actually executed (5 tests) rather than inferred from a green suite.

Two families read NOT MEASURED rather than green: check-test-completeness and check:dual-build-cjs-loads both exit 3 PREREQUISITE NOT MET (a saved turbo test log; a full pnpm build). Neither is a finding.

One red was found and fixed during this run, by the gate family rather than by review: the rewritten comment first used backtick-quoted text, and the page body lives inside a source: template literal, so the backticks terminated it early. check:logger-receiver-detach refused to scan the unparseable file. The comment now uses the quoting convention the rest of the file already used, and every gate was re-run afterwards on the final tree.

Notes for review

  • No changeset, skip-changeset applied. Nothing here publishes: @objectstack/example-showcase is private: true, and the other two paths are docs content and a root gate script. scripts/pr-labels.mjs:44 states the criterion as "the exemption for a PR that publishes nothing". Precedent on these same files: the gate-creating PR feat(tooling): sweep the react-page useAdapter() contracts over the docs corpus too #11584 and the previous repair of this exact docs line, 243218a36, both landed changeset-free. Happy to add one if this seat reads it differently.
  • The Array.isArray limb is deliberately left in both sites. It is also unreachable against the current contract — normalizeQueryResult wraps a bare array response — and the card raises it as an open question that the triage ruling did not answer. Deciding it here would have pre-empted that, so it is reported instead. See the report comment on the issue.
  • Hot-file boundary respected: docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers #12650's html-tier / kebab-case work in content/docs/ui/react-pages.mdx is untouched. This diff changes one line in that file.

Generated by Claude Code


Generated by Claude Code

…essed them
`ObjectStackAdapter.find()` resolves a normalized `QueryResult` whose sole
non-empty return path (`normalizeQueryResult`) builds an object literal with
exactly `data`, `total`, `page`, `pageSize`, `hasMore`. `records` is a key it
READS off the transport envelope, never one it writes -- so the `?? .records`
limb in both surviving repairs is unreachable by contract.
Behaviour-preserving: `.data` is read first and always wins today. What goes is
a spelling the producer cannot emit, sitting in the page a customer copies from.
The third piece is what stops it returning: `recordsOnlyReads()` skipped any
line carrying `.data`, which made `data ?? records` the one shape it could not
see. That carve-out is replaced by comment/string stripping, and the detector
renamed `recordsReads` since "only" no longer describes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 07:45
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 789aa68Aug 31, 2026
37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-11585-records-tolerant-aliases branch August 31, 2026 08:18
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
`@objectstack/docs` patch, following #13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and #13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
…etector (#13970)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (#11585 -> #13705 ->
#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…d()` sites (objectstack-ai#13969)
* Delete the unreachable Array.isArray limb at all three adapter.find sites
`ObjectStackAdapter.find()` cannot resolve to an array. Re-derived on the
pinned objectui sha (9602dc82) and on objectui `origin/main`: find() has two
object-literal returns (`{ data: [], total: 0 }` for a memoized 404 and for a
fresh non-denial 404), two `normalizeQueryResult(...)` returns, and an inflight
`return existing` that hands back a promise from that same set. Both of
`normalizeQueryResult`'s branches return an object literal with exactly
`data, total, page, pageSize, hasMore` -- the first one WRAPS a bare array
response into it. So no `Array.isArray(<find result>)` limb can ever be taken.
Behaviour-preserving, like the `?? records` deletion beside it: `.data` was
already read first and always won. What goes is a shape the producer cannot
emit, in the sample a customer (and a coding agent) copies from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
* Add the changeset for the docs sample change
`@objectstack/docs` patch, following objectstack-ai#13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and objectstack-ai#13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
---------
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…etector (objectstack-ai#13970) (objectstack-ai#13989)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (objectstack-ai#11585 -> objectstack-ai#13705 ->
objectstack-ai#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-objectstack-ai#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the surviving .records repairs are tolerant ?? records aliases, and the guard's .data-beside carve-out is what blesses them

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them - #13705

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases
Aug 31, 2026
Merged

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them#13705
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#11585

ObjectStackAdapter.find() resolves a normalized QueryResult that declares data and never records, so the ?? .records limb in both surviving repairs is unreachable by contract. This is behaviour-preserving.data is read first and always wins today, and both pages render correctly either way. What goes is a spelling the producer cannot emit, sitting in the page a customer copies from.

Three things ship, because the first two alone let the pattern come back green.

1 + 2 — the two tolerant aliases

  • examples/app-showcase/src/ui/pages/crm-workbench.page.ts:49(all && (all.data || all.records)) || [] becomes (all && all.data) || [].
  • content/docs/ui/react-pages.mdx:145result?.data ?? result?.records ?? ... loses the .records limb. This is the one that actively TEACHES the fallback.

The prose three lines above the app-showcase call said "Read .data first, with .records/array fallbacks for robustness". That sentence was the instruction the alias implemented, so it is rewritten to state the contract instead. The historical explanation of why .records was wrong is kept.

3 — the carve-out that blessed them

recordsOnlyReads() skipped any line carrying .data:

if(!trimmed.includes('.records'))continue;if(trimmed.includes('.data'))continue;// THIS ONE

That made data ?? records the one shape the detector could not see — which is precisely the shape both surviving repairs had landed as. The carve-out is replaced by comment and string-literal stripping (codeOnly()), so .records is judged as a property READ rather than as text that merely spells it. The detector is renamed recordsReads, since "only" no longer describes it.

Blanking string bodies is what lets the carve-out go safely: the webhook shape emit({ type: 'data.records.updated' }) is kept out of the sweep by the SELECTOR today, and a detector that is quiet only because of the selector is one population change away from firing.

Measured, not assumed

A .records PRODUCER search — none exists on this path.useAdapter() is typed ObjectStackAdapter | null (objectui packages/react/src/context/AppShellContext.tsx:10), so no other DataSource reaches a react page through it. ObjectStackAdapter.find() has exactly three return paths (objectui packages/data-objectstack/src/index.ts): two { data: [], total: 0 } literals, and normalizeQueryResult() at line 3461, which CONSUMES the transport envelope's records/value and returns an object literal with exactly data, total, page, pageSize, hasMore. records is a key it reads, never one it writes. The app-showcase's own contract-faithful adapter double returns the same five keys. The other .records shapes in the tree are different contracts and unreachable from these two sites: the @objectstack/clientPaginatedResult the adapter normalizes, the ObjectQL engine list shape in examples/app-showcase/src/automation/jobs/sweep-project-health.ts:107, ApiDataSource (which also extracts records INTO data), and the seed-authoring records: key in src/data/seed/index.ts. So the removal is behaviour-preserving at runtime, not just by declaration.

Guard population, before and after — zero bystanders. The sweep's population is unchanged: 21 app-showcase page modules + 1 content/docs react-page sample, from 395 doc files and 1946 fenced blocks, both before and after. Measuring the carve-out's load-bearing set over the guard's OWN population (importing collectSources, not re-guessing it) found it suppressing exactly two lines — the two deleted here — and nothing else.

Reverse-verified from the committed state: with the guard narrowed and both alias lines restored from the merge base, the gate exits 1 with exactly 2 findings, naming crm-workbench.page.ts:49 and react-pages.mdx:145 and no third file. The restore leg was proven byte-for-byte against the HEAD blobs. So the narrowing bites on the pattern and reds no unrelated existing code.

The self-test grows from 30 to 37 assertions: both aliases pinned as findings, the repaired docs sample pinned silent, and the over-fire directions pinned too — a string-literal data.records.updated, a trailing comment naming .records beside a canonical read, and .recordsCount.

Verification

Local gate union at 69e868cf9, the final commit. All 43 derived families green plus both convention-triggered gate-script obligations (bare-root-worklist --self-test; check:pm-dispatch-gates, 1017 cases). pnpm lint — the full-repo eslint . --no-inline-config — clean, run whole rather than narrowed. @objectstack/example-showcase: 26 files / 364 tests pass, typecheck clean, and test/react-page-adapter-query-contract.test.ts confirmed to have actually executed (5 tests) rather than inferred from a green suite.

Two families read NOT MEASURED rather than green: check-test-completeness and check:dual-build-cjs-loads both exit 3 PREREQUISITE NOT MET (a saved turbo test log; a full pnpm build). Neither is a finding.

One red was found and fixed during this run, by the gate family rather than by review: the rewritten comment first used backtick-quoted text, and the page body lives inside a source: template literal, so the backticks terminated it early. check:logger-receiver-detach refused to scan the unparseable file. The comment now uses the quoting convention the rest of the file already used, and every gate was re-run afterwards on the final tree.

Notes for review

  • No changeset, skip-changeset applied. Nothing here publishes: @objectstack/example-showcase is private: true, and the other two paths are docs content and a root gate script. scripts/pr-labels.mjs:44 states the criterion as "the exemption for a PR that publishes nothing". Precedent on these same files: the gate-creating PR feat(tooling): sweep the react-page useAdapter() contracts over the docs corpus too #11584 and the previous repair of this exact docs line, 243218a36, both landed changeset-free. Happy to add one if this seat reads it differently.
  • The Array.isArray limb is deliberately left in both sites. It is also unreachable against the current contract — normalizeQueryResult wraps a bare array response — and the card raises it as an open question that the triage ruling did not answer. Deciding it here would have pre-empted that, so it is reported instead. See the report comment on the issue.
  • Hot-file boundary respected: docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers #12650's html-tier / kebab-case work in content/docs/ui/react-pages.mdx is untouched. This diff changes one line in that file.

Generated by Claude Code


Generated by Claude Code

…essed them
`ObjectStackAdapter.find()` resolves a normalized `QueryResult` whose sole
non-empty return path (`normalizeQueryResult`) builds an object literal with
exactly `data`, `total`, `page`, `pageSize`, `hasMore`. `records` is a key it
READS off the transport envelope, never one it writes -- so the `?? .records`
limb in both surviving repairs is unreachable by contract.
Behaviour-preserving: `.data` is read first and always wins today. What goes is
a spelling the producer cannot emit, sitting in the page a customer copies from.
The third piece is what stops it returning: `recordsOnlyReads()` skipped any
line carrying `.data`, which made `data ?? records` the one shape it could not
see. That carve-out is replaced by comment/string stripping, and the detector
renamed `recordsReads` since "only" no longer describes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 07:45
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 789aa68Aug 31, 2026
37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-11585-records-tolerant-aliases branch August 31, 2026 08:18
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
`@objectstack/docs` patch, following #13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and #13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
…etector (#13970)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (#11585 -> #13705 ->
#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…d()` sites (objectstack-ai#13969)
* Delete the unreachable Array.isArray limb at all three adapter.find sites
`ObjectStackAdapter.find()` cannot resolve to an array. Re-derived on the
pinned objectui sha (9602dc82) and on objectui `origin/main`: find() has two
object-literal returns (`{ data: [], total: 0 }` for a memoized 404 and for a
fresh non-denial 404), two `normalizeQueryResult(...)` returns, and an inflight
`return existing` that hands back a promise from that same set. Both of
`normalizeQueryResult`'s branches return an object literal with exactly
`data, total, page, pageSize, hasMore` -- the first one WRAPS a bare array
response into it. So no `Array.isArray(<find result>)` limb can ever be taken.
Behaviour-preserving, like the `?? records` deletion beside it: `.data` was
already read first and always won. What goes is a shape the producer cannot
emit, in the sample a customer (and a coding agent) copies from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
* Add the changeset for the docs sample change
`@objectstack/docs` patch, following objectstack-ai#13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and objectstack-ai#13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
---------
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…etector (objectstack-ai#13970) (objectstack-ai#13989)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (objectstack-ai#11585 -> objectstack-ai#13705 ->
objectstack-ai#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-objectstack-ai#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the surviving .records repairs are tolerant ?? records aliases, and the guard's .data-beside carve-out is what blesses them

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them - #13705

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases
Aug 31, 2026
Merged

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them#13705
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#11585

ObjectStackAdapter.find() resolves a normalized QueryResult that declares data and never records, so the ?? .records limb in both surviving repairs is unreachable by contract. This is behaviour-preserving.data is read first and always wins today, and both pages render correctly either way. What goes is a spelling the producer cannot emit, sitting in the page a customer copies from.

Three things ship, because the first two alone let the pattern come back green.

1 + 2 — the two tolerant aliases

  • examples/app-showcase/src/ui/pages/crm-workbench.page.ts:49(all && (all.data || all.records)) || [] becomes (all && all.data) || [].
  • content/docs/ui/react-pages.mdx:145result?.data ?? result?.records ?? ... loses the .records limb. This is the one that actively TEACHES the fallback.

The prose three lines above the app-showcase call said "Read .data first, with .records/array fallbacks for robustness". That sentence was the instruction the alias implemented, so it is rewritten to state the contract instead. The historical explanation of why .records was wrong is kept.

3 — the carve-out that blessed them

recordsOnlyReads() skipped any line carrying .data:

if(!trimmed.includes('.records'))continue;if(trimmed.includes('.data'))continue;// THIS ONE

That made data ?? records the one shape the detector could not see — which is precisely the shape both surviving repairs had landed as. The carve-out is replaced by comment and string-literal stripping (codeOnly()), so .records is judged as a property READ rather than as text that merely spells it. The detector is renamed recordsReads, since "only" no longer describes it.

Blanking string bodies is what lets the carve-out go safely: the webhook shape emit({ type: 'data.records.updated' }) is kept out of the sweep by the SELECTOR today, and a detector that is quiet only because of the selector is one population change away from firing.

Measured, not assumed

A .records PRODUCER search — none exists on this path.useAdapter() is typed ObjectStackAdapter | null (objectui packages/react/src/context/AppShellContext.tsx:10), so no other DataSource reaches a react page through it. ObjectStackAdapter.find() has exactly three return paths (objectui packages/data-objectstack/src/index.ts): two { data: [], total: 0 } literals, and normalizeQueryResult() at line 3461, which CONSUMES the transport envelope's records/value and returns an object literal with exactly data, total, page, pageSize, hasMore. records is a key it reads, never one it writes. The app-showcase's own contract-faithful adapter double returns the same five keys. The other .records shapes in the tree are different contracts and unreachable from these two sites: the @objectstack/clientPaginatedResult the adapter normalizes, the ObjectQL engine list shape in examples/app-showcase/src/automation/jobs/sweep-project-health.ts:107, ApiDataSource (which also extracts records INTO data), and the seed-authoring records: key in src/data/seed/index.ts. So the removal is behaviour-preserving at runtime, not just by declaration.

Guard population, before and after — zero bystanders. The sweep's population is unchanged: 21 app-showcase page modules + 1 content/docs react-page sample, from 395 doc files and 1946 fenced blocks, both before and after. Measuring the carve-out's load-bearing set over the guard's OWN population (importing collectSources, not re-guessing it) found it suppressing exactly two lines — the two deleted here — and nothing else.

Reverse-verified from the committed state: with the guard narrowed and both alias lines restored from the merge base, the gate exits 1 with exactly 2 findings, naming crm-workbench.page.ts:49 and react-pages.mdx:145 and no third file. The restore leg was proven byte-for-byte against the HEAD blobs. So the narrowing bites on the pattern and reds no unrelated existing code.

The self-test grows from 30 to 37 assertions: both aliases pinned as findings, the repaired docs sample pinned silent, and the over-fire directions pinned too — a string-literal data.records.updated, a trailing comment naming .records beside a canonical read, and .recordsCount.

Verification

Local gate union at 69e868cf9, the final commit. All 43 derived families green plus both convention-triggered gate-script obligations (bare-root-worklist --self-test; check:pm-dispatch-gates, 1017 cases). pnpm lint — the full-repo eslint . --no-inline-config — clean, run whole rather than narrowed. @objectstack/example-showcase: 26 files / 364 tests pass, typecheck clean, and test/react-page-adapter-query-contract.test.ts confirmed to have actually executed (5 tests) rather than inferred from a green suite.

Two families read NOT MEASURED rather than green: check-test-completeness and check:dual-build-cjs-loads both exit 3 PREREQUISITE NOT MET (a saved turbo test log; a full pnpm build). Neither is a finding.

One red was found and fixed during this run, by the gate family rather than by review: the rewritten comment first used backtick-quoted text, and the page body lives inside a source: template literal, so the backticks terminated it early. check:logger-receiver-detach refused to scan the unparseable file. The comment now uses the quoting convention the rest of the file already used, and every gate was re-run afterwards on the final tree.

Notes for review

  • No changeset, skip-changeset applied. Nothing here publishes: @objectstack/example-showcase is private: true, and the other two paths are docs content and a root gate script. scripts/pr-labels.mjs:44 states the criterion as "the exemption for a PR that publishes nothing". Precedent on these same files: the gate-creating PR feat(tooling): sweep the react-page useAdapter() contracts over the docs corpus too #11584 and the previous repair of this exact docs line, 243218a36, both landed changeset-free. Happy to add one if this seat reads it differently.
  • The Array.isArray limb is deliberately left in both sites. It is also unreachable against the current contract — normalizeQueryResult wraps a bare array response — and the card raises it as an open question that the triage ruling did not answer. Deciding it here would have pre-empted that, so it is reported instead. See the report comment on the issue.
  • Hot-file boundary respected: docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers #12650's html-tier / kebab-case work in content/docs/ui/react-pages.mdx is untouched. This diff changes one line in that file.

Generated by Claude Code


Generated by Claude Code

…essed them
`ObjectStackAdapter.find()` resolves a normalized `QueryResult` whose sole
non-empty return path (`normalizeQueryResult`) builds an object literal with
exactly `data`, `total`, `page`, `pageSize`, `hasMore`. `records` is a key it
READS off the transport envelope, never one it writes -- so the `?? .records`
limb in both surviving repairs is unreachable by contract.
Behaviour-preserving: `.data` is read first and always wins today. What goes is
a spelling the producer cannot emit, sitting in the page a customer copies from.
The third piece is what stops it returning: `recordsOnlyReads()` skipped any
line carrying `.data`, which made `data ?? records` the one shape it could not
see. That carve-out is replaced by comment/string stripping, and the detector
renamed `recordsReads` since "only" no longer describes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 07:45
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 789aa68Aug 31, 2026
37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-11585-records-tolerant-aliases branch August 31, 2026 08:18
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
`@objectstack/docs` patch, following #13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and #13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
…etector (#13970)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (#11585 -> #13705 ->
#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…d()` sites (objectstack-ai#13969)
* Delete the unreachable Array.isArray limb at all three adapter.find sites
`ObjectStackAdapter.find()` cannot resolve to an array. Re-derived on the
pinned objectui sha (9602dc82) and on objectui `origin/main`: find() has two
object-literal returns (`{ data: [], total: 0 }` for a memoized 404 and for a
fresh non-denial 404), two `normalizeQueryResult(...)` returns, and an inflight
`return existing` that hands back a promise from that same set. Both of
`normalizeQueryResult`'s branches return an object literal with exactly
`data, total, page, pageSize, hasMore` -- the first one WRAPS a bare array
response into it. So no `Array.isArray(<find result>)` limb can ever be taken.
Behaviour-preserving, like the `?? records` deletion beside it: `.data` was
already read first and always won. What goes is a shape the producer cannot
emit, in the sample a customer (and a coding agent) copies from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
* Add the changeset for the docs sample change
`@objectstack/docs` patch, following objectstack-ai#13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and objectstack-ai#13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
---------
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…etector (objectstack-ai#13970) (objectstack-ai#13989)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (objectstack-ai#11585 -> objectstack-ai#13705 ->
objectstack-ai#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-objectstack-ai#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the surviving .records repairs are tolerant ?? records aliases, and the guard's .data-beside carve-out is what blesses them

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them - #13705

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases
Aug 31, 2026
Merged

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them#13705
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#11585

ObjectStackAdapter.find() resolves a normalized QueryResult that declares data and never records, so the ?? .records limb in both surviving repairs is unreachable by contract. This is behaviour-preserving.data is read first and always wins today, and both pages render correctly either way. What goes is a spelling the producer cannot emit, sitting in the page a customer copies from.

Three things ship, because the first two alone let the pattern come back green.

1 + 2 — the two tolerant aliases

  • examples/app-showcase/src/ui/pages/crm-workbench.page.ts:49(all && (all.data || all.records)) || [] becomes (all && all.data) || [].
  • content/docs/ui/react-pages.mdx:145result?.data ?? result?.records ?? ... loses the .records limb. This is the one that actively TEACHES the fallback.

The prose three lines above the app-showcase call said "Read .data first, with .records/array fallbacks for robustness". That sentence was the instruction the alias implemented, so it is rewritten to state the contract instead. The historical explanation of why .records was wrong is kept.

3 — the carve-out that blessed them

recordsOnlyReads() skipped any line carrying .data:

if(!trimmed.includes('.records'))continue;if(trimmed.includes('.data'))continue;// THIS ONE

That made data ?? records the one shape the detector could not see — which is precisely the shape both surviving repairs had landed as. The carve-out is replaced by comment and string-literal stripping (codeOnly()), so .records is judged as a property READ rather than as text that merely spells it. The detector is renamed recordsReads, since "only" no longer describes it.

Blanking string bodies is what lets the carve-out go safely: the webhook shape emit({ type: 'data.records.updated' }) is kept out of the sweep by the SELECTOR today, and a detector that is quiet only because of the selector is one population change away from firing.

Measured, not assumed

A .records PRODUCER search — none exists on this path.useAdapter() is typed ObjectStackAdapter | null (objectui packages/react/src/context/AppShellContext.tsx:10), so no other DataSource reaches a react page through it. ObjectStackAdapter.find() has exactly three return paths (objectui packages/data-objectstack/src/index.ts): two { data: [], total: 0 } literals, and normalizeQueryResult() at line 3461, which CONSUMES the transport envelope's records/value and returns an object literal with exactly data, total, page, pageSize, hasMore. records is a key it reads, never one it writes. The app-showcase's own contract-faithful adapter double returns the same five keys. The other .records shapes in the tree are different contracts and unreachable from these two sites: the @objectstack/clientPaginatedResult the adapter normalizes, the ObjectQL engine list shape in examples/app-showcase/src/automation/jobs/sweep-project-health.ts:107, ApiDataSource (which also extracts records INTO data), and the seed-authoring records: key in src/data/seed/index.ts. So the removal is behaviour-preserving at runtime, not just by declaration.

Guard population, before and after — zero bystanders. The sweep's population is unchanged: 21 app-showcase page modules + 1 content/docs react-page sample, from 395 doc files and 1946 fenced blocks, both before and after. Measuring the carve-out's load-bearing set over the guard's OWN population (importing collectSources, not re-guessing it) found it suppressing exactly two lines — the two deleted here — and nothing else.

Reverse-verified from the committed state: with the guard narrowed and both alias lines restored from the merge base, the gate exits 1 with exactly 2 findings, naming crm-workbench.page.ts:49 and react-pages.mdx:145 and no third file. The restore leg was proven byte-for-byte against the HEAD blobs. So the narrowing bites on the pattern and reds no unrelated existing code.

The self-test grows from 30 to 37 assertions: both aliases pinned as findings, the repaired docs sample pinned silent, and the over-fire directions pinned too — a string-literal data.records.updated, a trailing comment naming .records beside a canonical read, and .recordsCount.

Verification

Local gate union at 69e868cf9, the final commit. All 43 derived families green plus both convention-triggered gate-script obligations (bare-root-worklist --self-test; check:pm-dispatch-gates, 1017 cases). pnpm lint — the full-repo eslint . --no-inline-config — clean, run whole rather than narrowed. @objectstack/example-showcase: 26 files / 364 tests pass, typecheck clean, and test/react-page-adapter-query-contract.test.ts confirmed to have actually executed (5 tests) rather than inferred from a green suite.

Two families read NOT MEASURED rather than green: check-test-completeness and check:dual-build-cjs-loads both exit 3 PREREQUISITE NOT MET (a saved turbo test log; a full pnpm build). Neither is a finding.

One red was found and fixed during this run, by the gate family rather than by review: the rewritten comment first used backtick-quoted text, and the page body lives inside a source: template literal, so the backticks terminated it early. check:logger-receiver-detach refused to scan the unparseable file. The comment now uses the quoting convention the rest of the file already used, and every gate was re-run afterwards on the final tree.

Notes for review

  • No changeset, skip-changeset applied. Nothing here publishes: @objectstack/example-showcase is private: true, and the other two paths are docs content and a root gate script. scripts/pr-labels.mjs:44 states the criterion as "the exemption for a PR that publishes nothing". Precedent on these same files: the gate-creating PR feat(tooling): sweep the react-page useAdapter() contracts over the docs corpus too #11584 and the previous repair of this exact docs line, 243218a36, both landed changeset-free. Happy to add one if this seat reads it differently.
  • The Array.isArray limb is deliberately left in both sites. It is also unreachable against the current contract — normalizeQueryResult wraps a bare array response — and the card raises it as an open question that the triage ruling did not answer. Deciding it here would have pre-empted that, so it is reported instead. See the report comment on the issue.
  • Hot-file boundary respected: docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers #12650's html-tier / kebab-case work in content/docs/ui/react-pages.mdx is untouched. This diff changes one line in that file.

Generated by Claude Code


Generated by Claude Code

…essed them
`ObjectStackAdapter.find()` resolves a normalized `QueryResult` whose sole
non-empty return path (`normalizeQueryResult`) builds an object literal with
exactly `data`, `total`, `page`, `pageSize`, `hasMore`. `records` is a key it
READS off the transport envelope, never one it writes -- so the `?? .records`
limb in both surviving repairs is unreachable by contract.
Behaviour-preserving: `.data` is read first and always wins today. What goes is
a spelling the producer cannot emit, sitting in the page a customer copies from.
The third piece is what stops it returning: `recordsOnlyReads()` skipped any
line carrying `.data`, which made `data ?? records` the one shape it could not
see. That carve-out is replaced by comment/string stripping, and the detector
renamed `recordsReads` since "only" no longer describes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 07:45
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 789aa68Aug 31, 2026
37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-11585-records-tolerant-aliases branch August 31, 2026 08:18
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
`@objectstack/docs` patch, following #13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and #13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
…etector (#13970)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (#11585 -> #13705 ->
#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…d()` sites (objectstack-ai#13969)
* Delete the unreachable Array.isArray limb at all three adapter.find sites
`ObjectStackAdapter.find()` cannot resolve to an array. Re-derived on the
pinned objectui sha (9602dc82) and on objectui `origin/main`: find() has two
object-literal returns (`{ data: [], total: 0 }` for a memoized 404 and for a
fresh non-denial 404), two `normalizeQueryResult(...)` returns, and an inflight
`return existing` that hands back a promise from that same set. Both of
`normalizeQueryResult`'s branches return an object literal with exactly
`data, total, page, pageSize, hasMore` -- the first one WRAPS a bare array
response into it. So no `Array.isArray(<find result>)` limb can ever be taken.
Behaviour-preserving, like the `?? records` deletion beside it: `.data` was
already read first and always won. What goes is a shape the producer cannot
emit, in the sample a customer (and a coding agent) copies from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
* Add the changeset for the docs sample change
`@objectstack/docs` patch, following objectstack-ai#13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and objectstack-ai#13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
---------
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…etector (objectstack-ai#13970) (objectstack-ai#13989)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (objectstack-ai#11585 -> objectstack-ai#13705 ->
objectstack-ai#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-objectstack-ai#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the surviving .records repairs are tolerant ?? records aliases, and the guard's .data-beside carve-out is what blesses them

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them - #13705

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases
Aug 31, 2026
Merged

Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them#13705
os-project-manager merged 1 commit into
mainfrom
claude/issue-11585-records-tolerant-aliases

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#11585

ObjectStackAdapter.find() resolves a normalized QueryResult that declares data and never records, so the ?? .records limb in both surviving repairs is unreachable by contract. This is behaviour-preserving.data is read first and always wins today, and both pages render correctly either way. What goes is a spelling the producer cannot emit, sitting in the page a customer copies from.

Three things ship, because the first two alone let the pattern come back green.

1 + 2 — the two tolerant aliases

  • examples/app-showcase/src/ui/pages/crm-workbench.page.ts:49(all && (all.data || all.records)) || [] becomes (all && all.data) || [].
  • content/docs/ui/react-pages.mdx:145result?.data ?? result?.records ?? ... loses the .records limb. This is the one that actively TEACHES the fallback.

The prose three lines above the app-showcase call said "Read .data first, with .records/array fallbacks for robustness". That sentence was the instruction the alias implemented, so it is rewritten to state the contract instead. The historical explanation of why .records was wrong is kept.

3 — the carve-out that blessed them

recordsOnlyReads() skipped any line carrying .data:

if(!trimmed.includes('.records'))continue;if(trimmed.includes('.data'))continue;// THIS ONE

That made data ?? records the one shape the detector could not see — which is precisely the shape both surviving repairs had landed as. The carve-out is replaced by comment and string-literal stripping (codeOnly()), so .records is judged as a property READ rather than as text that merely spells it. The detector is renamed recordsReads, since "only" no longer describes it.

Blanking string bodies is what lets the carve-out go safely: the webhook shape emit({ type: 'data.records.updated' }) is kept out of the sweep by the SELECTOR today, and a detector that is quiet only because of the selector is one population change away from firing.

Measured, not assumed

A .records PRODUCER search — none exists on this path.useAdapter() is typed ObjectStackAdapter | null (objectui packages/react/src/context/AppShellContext.tsx:10), so no other DataSource reaches a react page through it. ObjectStackAdapter.find() has exactly three return paths (objectui packages/data-objectstack/src/index.ts): two { data: [], total: 0 } literals, and normalizeQueryResult() at line 3461, which CONSUMES the transport envelope's records/value and returns an object literal with exactly data, total, page, pageSize, hasMore. records is a key it reads, never one it writes. The app-showcase's own contract-faithful adapter double returns the same five keys. The other .records shapes in the tree are different contracts and unreachable from these two sites: the @objectstack/clientPaginatedResult the adapter normalizes, the ObjectQL engine list shape in examples/app-showcase/src/automation/jobs/sweep-project-health.ts:107, ApiDataSource (which also extracts records INTO data), and the seed-authoring records: key in src/data/seed/index.ts. So the removal is behaviour-preserving at runtime, not just by declaration.

Guard population, before and after — zero bystanders. The sweep's population is unchanged: 21 app-showcase page modules + 1 content/docs react-page sample, from 395 doc files and 1946 fenced blocks, both before and after. Measuring the carve-out's load-bearing set over the guard's OWN population (importing collectSources, not re-guessing it) found it suppressing exactly two lines — the two deleted here — and nothing else.

Reverse-verified from the committed state: with the guard narrowed and both alias lines restored from the merge base, the gate exits 1 with exactly 2 findings, naming crm-workbench.page.ts:49 and react-pages.mdx:145 and no third file. The restore leg was proven byte-for-byte against the HEAD blobs. So the narrowing bites on the pattern and reds no unrelated existing code.

The self-test grows from 30 to 37 assertions: both aliases pinned as findings, the repaired docs sample pinned silent, and the over-fire directions pinned too — a string-literal data.records.updated, a trailing comment naming .records beside a canonical read, and .recordsCount.

Verification

Local gate union at 69e868cf9, the final commit. All 43 derived families green plus both convention-triggered gate-script obligations (bare-root-worklist --self-test; check:pm-dispatch-gates, 1017 cases). pnpm lint — the full-repo eslint . --no-inline-config — clean, run whole rather than narrowed. @objectstack/example-showcase: 26 files / 364 tests pass, typecheck clean, and test/react-page-adapter-query-contract.test.ts confirmed to have actually executed (5 tests) rather than inferred from a green suite.

Two families read NOT MEASURED rather than green: check-test-completeness and check:dual-build-cjs-loads both exit 3 PREREQUISITE NOT MET (a saved turbo test log; a full pnpm build). Neither is a finding.

One red was found and fixed during this run, by the gate family rather than by review: the rewritten comment first used backtick-quoted text, and the page body lives inside a source: template literal, so the backticks terminated it early. check:logger-receiver-detach refused to scan the unparseable file. The comment now uses the quoting convention the rest of the file already used, and every gate was re-run afterwards on the final tree.

Notes for review

  • No changeset, skip-changeset applied. Nothing here publishes: @objectstack/example-showcase is private: true, and the other two paths are docs content and a root gate script. scripts/pr-labels.mjs:44 states the criterion as "the exemption for a PR that publishes nothing". Precedent on these same files: the gate-creating PR feat(tooling): sweep the react-page useAdapter() contracts over the docs corpus too #11584 and the previous repair of this exact docs line, 243218a36, both landed changeset-free. Happy to add one if this seat reads it differently.
  • The Array.isArray limb is deliberately left in both sites. It is also unreachable against the current contract — normalizeQueryResult wraps a bare array response — and the card raises it as an open question that the triage ruling did not answer. Deciding it here would have pre-empted that, so it is reported instead. See the report comment on the issue.
  • Hot-file boundary respected: docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers #12650's html-tier / kebab-case work in content/docs/ui/react-pages.mdx is untouched. This diff changes one line in that file.

Generated by Claude Code


Generated by Claude Code

…essed them
`ObjectStackAdapter.find()` resolves a normalized `QueryResult` whose sole
non-empty return path (`normalizeQueryResult`) builds an object literal with
exactly `data`, `total`, `page`, `pageSize`, `hasMore`. `records` is a key it
READS off the transport envelope, never one it writes -- so the `?? .records`
limb in both surviving repairs is unreachable by contract.
Behaviour-preserving: `.data` is read first and always wins today. What goes is
a spelling the producer cannot emit, sitting in the page a customer copies from.
The third piece is what stops it returning: `recordsOnlyReads()` skipped any
line carrying `.data`, which made `data ?? records` the one shape it could not
see. That carve-out is replaced by comment/string stripping, and the detector
renamed `recordsReads` since "only" no longer describes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 07:45
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 789aa68Aug 31, 2026
37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-11585-records-tolerant-aliases branch August 31, 2026 08:18
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
`@objectstack/docs` patch, following #13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and #13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager pushed a commit that referenced this pull request Aug 31, 2026
…etector (#13970)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (#11585 -> #13705 ->
#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…d()` sites (objectstack-ai#13969)
* Delete the unreachable Array.isArray limb at all three adapter.find sites
`ObjectStackAdapter.find()` cannot resolve to an array. Re-derived on the
pinned objectui sha (9602dc82) and on objectui `origin/main`: find() has two
object-literal returns (`{ data: [], total: 0 }` for a memoized 404 and for a
fresh non-denial 404), two `normalizeQueryResult(...)` returns, and an inflight
`return existing` that hands back a promise from that same set. Both of
`normalizeQueryResult`'s branches return an object literal with exactly
`data, total, page, pageSize, hasMore` -- the first one WRAPS a bare array
response into it. So no `Array.isArray(<find result>)` limb can ever be taken.
Behaviour-preserving, like the `?? records` deletion beside it: `.data` was
already read first and always won. What goes is a shape the producer cannot
emit, in the sample a customer (and a coding agent) copies from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
* Add the changeset for the docs sample change
`@objectstack/docs` patch, following objectstack-ai#13955 (the most recent `content/docs/**`
diff, which named that package). `@objectstack/example-showcase` is deliberately
not named: it is private and appears in 0 of the repo's changesets, and objectstack-ai#13705 —
the immediately preceding repair at two of these same three sites — carried no
changeset at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
---------
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…etector (objectstack-ai#13970) (objectstack-ai#13989)
`ObjectStackAdapter.find()` cannot resolve to an array, so an
`Array.isArray(<find result>)` limb is dead code that teaches a row shape the
producer cannot emit. The shape was repaired three times (objectstack-ai#11585 -> objectstack-ai#13705 ->
objectstack-ai#13969) and each repair left nothing pinning it, so a reintroduction at any of
the three sites got a green guard.
`arrayIsArrayLimbs` is a THIRD detector, not a widening of `recordsReads`. The
self-test case that pins `const records = result?.data ?? (Array.isArray(result)
? result : []);` to zero `recordsReads` findings is a false-positive control on
the `.records` PROPERTY matcher — the `records` there is a local — and it is
still correct. It is unchanged, byte for byte: the new detector sees the limb on
that same line, and both statements are true.
A subject qualifies on either route: bound from an `adapter`/`dataSource`
find() in the same source, or read as `<subject>.data` on the same line. The
second route is load-bearing — the renewals-pipeline repair's subject was a
lambda parameter bound to no find() call anywhere.
Measured over the pre-objectstack-ai#13969 tree (bd8791f): exactly the three deleted sites
across the whole population, no false positives. On `origin/main` today: zero.
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the surviving .records repairs are tolerant ?? records aliases, and the guard's .data-beside carve-out is what blesses them

2 participants

@os-project-manager@claude