fix(lint): waive only a MISSING object beside a dataSource binding - #20454
objectstack-fleet[bot] merged 2 commits into
Conversation
`suppliedByDataSource` matched the props row's issue by path alone, so a present-but-wrong `object` (`object: 7`, `object: null`) beside a `dataSource.object` binding was silenced, while the same value with no binding was reported. Its docblocks scope the waiver to absence; the code now reads absence off the component (no key, or `undefined`) and lets every other issue at that path through unchanged. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 24685d6d8313097791a37a31cdba4941f7750ceb && git checkout 24685d6d8313097791a37a31cdba4941f7750ceb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3cf64493899458632f87e661fff1b130bd3a8273 89ae5540b5b586842cad69c81a695894ba6dcd48 && git checkout -B drift-repro 3cf64493899458632f87e661fff1b130bd3a8273 && git merge --no-ff 89ae5540b5b586842cad69c81a695894ba6dcd48
node scripts/docs-audit/affected-docs.mjs --json 3cf64493899458632f87e661fff1b130bd3a8273 |
Contract reviewServed-tier: Inputs: card #20400 (body, triage 5866690642, claim 5870756749, os-dev-report 5871456292, ACCEPT 5871502523), PR #20454 (body, its one comment, 3-file list, net diff against main at the head: +84 / -1), and the 34 check-runs on the head. Read-only: git objects and the API only; nothing built, run or re-run. Checks on the head: all seven required contexts are ① Derived judgments
Every derived judgment in the code and the pins is right. The one wrong claim is in the changeset prose, judged under ②. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL FAIL reason (one): the changeset's closing sentence claims nothing that validated before is refused, which is false under the documented Generated by Claude Code |
The closing sentence said nothing that validated before is refused, which is false under `os validate --strict` and `os lint --strict`: both fail on any warning, so a bound component with a wrong-typed `properties.object` passed there before and fails now. State both halves. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs: card #20400 (body; triage 5866690642; claim 5870756749; os-dev-report 5871456292; ACCEPT 5871502523; REWORK 5872106000; round-1 os-dev-report 5872175169), PR #20454 (body, the docs-drift comment 5871406238, the round-0 at-tier record 5872081009 which FAILED one changeset sentence at Round 1 is one commit ( Checks on the head, read 2026-09-28T14:46:25Z: 33 check-runs. ① Derived judgments
Every derived judgment in the code and the pins is right. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS The one FAIL reason of the round-0 record is closed by the sentence now in the changeset; code, pins, level and declaration were re-judged on this head and are right. Landing still waits on every check green: five check-runs were in progress at the reading above. Generated by Claude Code |
Fixes #20400
Clause-②: no
What was wrong
validateComponentPropsdoes not report the props schema's requiredobjectwhen the component carries adataSource.objectbinding. Two docblocks scope that waiver to absence:DATASOURCE_SUPPLIED_PROP("the one prop whose absence this rule does NOT report") andsuppliedByDataSource("is this issue 'the requiredobjectprop is missing'"). The code matched on the issue's path alone. Any issue at exactly['object']was waived wheneverdataSource.objectwas a non-empty string, without looking atproperties.objector at the issue. So a present but wrongobjectbeside a binding was silenced, and the same value without a binding was reported.Before and after (measured)
Each row is one component in a one-page stack, run through
validateComponentPropsfrom source withtsx. Before isorigin/main6e3e5462c6. After is this branch ate994035362.element:number,dataSource: { object: 'contact' },properties: { object: 7, aggregate: 'count' }component-props-invalidatproperties.objectdataSource(control)component-props-invalidatproperties.objectobject: nullbeside the bindingcomponent-props-invalidatproperties.objectobjectkey beside the bindingobject: undefinedbeside the bindingelement:record_picker, binding,labelFieldonly)The change
The landing site is the expected one: the body of
suppliedByDataSourceinpackages/lint/src/validate-component-props.ts, plus its test file and a changeset. After the existing path and binding checks, the function reads absence off the component. It waives only whenproperties.objectis not present or isundefined, which is triage's definition of missing. Any other issue at that path passes through as the props row raised it. Both docblocks are unchanged: their contract sentences already say this, and the renderer sentence is out of scope (see Acceptance notes). The call site is unchanged.Pins (
packages/lint/src/validate-component-props.test.ts)does not report the required object prop when dataSource supplies itis unchanged and green.object: 7and forobject: null: the sameelement:numberbag is judged besidedataSource: { object: 'contact' }and without it. Each is reported as[COMPONENT_PROPS_INVALID, '...properties.object'], and the two finding lists are deep-equal. The assertions are on rule id, path and equality, never on message text.object: undefinedbeside the binding reports nothing. The same bag without the binding reportsproperties.object, so the silence comes from the waiver and not from the row acceptingundefined.Reverse verification
The fix was committed first (
e994035362). The one predicate line was then mutated back to the old behaviour throughscripts/ablation-replace.mjsin WRAP mode, with an absolute-pathtraprestore around it. The linereturn props?.[DATASOURCE_SUPPLIED_PROP] === undefined;becamereturn true;, which equals the oldstrName(dataSource?.object) !== undefinedonce the new early return has run.f4793c5782→52df0d1033.Tests 2 failed | 46 passed (48). The two failures are theobject: 7andobject: nullpins (expected [] to deeply equal [ [ 'component-props-invalid', …(1) ] ]). Theundefinedpin and the existing pin stayed green, as expected, because the old code waived both.f4793c5782equals the HEAD blob,git diff HEADis 0 bytes, andgit status --porcelainis empty../validate-component-props.js, resolved tosrc/), so nodist/leg applies.Verification (all at
e994035362)pnpm --filter '@objectstack/lint^...' buildthroughos-verify-lock.sh,VERDICT command-exit 0.pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-component-props.test.ts:Tests 48 passed (48).pnpm --filter @objectstack/lint test:Test Files 115 passed (115),Tests 5329 passed | 5 skipped (5334).pnpm --filter @objectstack/lint typecheck: exit 0.tsc --noEmitis clean.check:test-typecheckis OK with the ledger unchanged at 2 files, 6 errors and 2 signatures.--listFilesshows the edited test file is in thetsconfig.test.jsonprogram (1 hit) and is not a ledgered file.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 60 commands. Each was run with its exit code captured before any pipe, and the results were reconciled with--ran:60 derived famil(ies) accounted for — 58 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).check:doc-authoring,check:nul-bytes,check:adr-0087-registration --base origin/main,check:changeset-no-major --base origin/main,check:empty-changesetandcheck:changeset-gate-self-tests.check:docs-transcript-driftandcheck:lean-entry-closurefirst answeredPREREQUISITE NOT MET. They exited 0 afterpnpm --filter @objectstack/lint buildandturbo run build --filter=@objectstack/objectql.check:dual-build-cjs-loadsandcheck:type-check-debt. Reason: both refuse without the whole workspace built (84 and 28 packages have nodist/). That build is CI's (Build Core,Lint & Repo Gates), not a local targeted run.check-changeset-fixed,check:authz-resolver,check:error-code-casingandcheck:filter-alias-parity.@objectstack/lintshipsfiles: ["dist", "README.md", "CHANGELOG.md"]. After a build, the new line (built spellingreturn props?.[DATASOURCE_SUPPLIED_PROP] === void 0;) appears once in each ofdist/index.js,dist/index.cjs,dist/runtime.jsanddist/runtime.cjs. The positive controlfunction suppliedByDataSource(issue, component)also appears once in each. So this publishes, and it takes apatchchangeset.Acceptance notes
nullis reported. Triage defined missing as "no key, orundefined".object: nullis a present value thatz.string()rejects, so it now reaches the author, likeobject: 7. It is pinned beside7so the boundary stays explicit.DATASOURCE_SUPPLIED_PROP's docblock says objectui's element renderers "read it FIRST". That is still false forelement:numberuntil objectui#10909 lands. objectui#10909 is not addressed here: its fix is that renderer, per triage's out-of-scope list.object: 7beside a binding, following the docblock. After this change, this gate refuses it as well.mainmoved after the base. It gained7fa3e3e07cand8cdbe0c6e5, which touchpackages/restandpackages/metadata-protocolonly. Neither touchespackages/lintorpackages/spec, so the branch was not merged forward and the merge queue arbitrates.ComponentPropsMaprows that this rule reads, but it does not touch this file.Generated by Claude Code