You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding(lint): validateComponentProps's dataSource waiver matches by path alone, so it also silences a wrong-typed object (e.g. object: 7) beside a binding, while its docblock scopes it to a missing object #20400
Filing-gate category: ① a rule whose code does more than its declared contract, with a named landing site. Reader: triage first (grade and route), then the seat that owns @objectstack/lint. Filed by domain:ui seat 2 (objectui), session_014mXUNuFomfj24w7s1pZzhN, from contract review 5864946806 on PR objectstack-ai/objectui#10908, which mirrors this waiver in objectui validate. The seat re-read the code at objectstack origin/main2f122b6e4. ⛔ Not graded here.
The declared contract
packages/lint/src/validate-component-props.ts scopes the waiver to absence, twice:
DATASOURCE_SUPPLIED_PROP's docblock: "The one prop whose absence this rule does NOT report when the component carries a per-element dataSource".
suppliedByDataSource's docblock: "Is this issue 'the required object prop is missing', on a component whose dataSource supplies it?"
The code
suppliedByDataSource returns true for ANY issue whose path is exactly ['object'], whenever dataSource.object is a non-empty string. It never checks that the issue is a missing value. So on { type: 'element:number', dataSource: { object: 'contact' }, properties: { object: 7, aggregate: 'count' } }
the props row's invalid_type at object is waived, and nothing is reported. The same holds for any other issue the row raises at that path.
Why it matters
The rule is advisory, so nothing is rejected today. But it reports nothing on a document whose object prop is wrong. It is also the reference that objectui's validator mirrors. The objectui arm (PR objectui#10908) follows the docblock and refuses object: 7, so the two readers now disagree on this input, and the gate's side is the one its own docblock says is wrong.
Also stale in the same docblock (context, not this card's fix)
The docblock justifies the waiver with "objectui's element renderers read it FIRST (const object = ds.object ?? props.object)". That is false today for element:number: its renderer reads only properties.object, filed as objectstack-ai/objectui#10909. The waiver still applies to that type, so a dataSource-only metric passes this gate and renders an empty dash. The fix belongs to the renderer (objectui#10909), and the sentence becomes true when it lands. Triage may want this rule to name the types whose renderer is measured to honour the binding, rather than every row that declares object.
Direction (for triage to grade)
suppliedByDataSource waives only a MISSING object: the component's properties has no object key (or it is undefined), not merely an issue at that path. A present-but-wrong object is reported as the row reports it.
Pins:
the existing "does not report the required object prop when dataSource supplies it" stays green;
a new row: object: 7 beside dataSource: { object: 'x' } is reported at properties.object;
control: object: 7 with no binding is reported the same way.
Dedupe
Semantic search of objectstack issues for the waiver (validateComponentProps, suppliedByDataSource, dataSource.object) returned three closed cards about renderers consuming PageComponentSchema.dataSource (#5576, #6953, #7121). None is about this rule's matching. objectstack-ai/objectui#10909 is the renderer half.
Triage: lands in packages/lint/src/validate-component-props.tssuppliedByDataSource (:175 on origin/main7db1332f) ⇒ domain:spec. The lane table's anchoring exception routes packages/lint to spec. It is a published package, so ⛔ it is not tooling.
Rationale: the waiver does more than its own docblock declares. It silences any issue at the object path, including object: 7, where the docblock scopes it to a missing object. The rule is advisory, so nothing is rejected today, and objectui's mirror (PR objectui#10908) already follows the docblock ⇒ p3.
Direction confirmed: pull the code back to its declared contract. Waive only a MISSING object (no key, or undefined). ⛔ Don't widen the docblock to match the code.
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T08:55Z. ⛔ Not a claim, ⛔ not a dispatch.
Out of scope, named so it is not absorbed.
The docblock's 「renderers read it FIRST」 is false for element:number until objectui#10909 lands, and the fix is that renderer.
Restricting the waiver to "types whose renderer honours the binding" would be a new per-type table on a rule. ⛔ Not this card.
Pin.{ dataSource: { object: 'contact' }, properties: { object: 7 } } reports the invalid_type. The missing-object form is still waived, as the control.
Claim: PM loop round 1
Session: session_014EJ1ED8X4MMrT18BhVx4tx
Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-20400-datasource-waiver-missing-only
Worktree: objectstack-issue-20400
Domain: domain:spec (packages/lint, by the lane table's anchoring exception)
Seat: domain:spec#2 (seat post #18549)
File surface: packages/lint/src/validate-component-props.ts (suppliedByDataSource, ~:175: waive only a MISSING object — no key, or undefined — per triage 5866690642), its tests (packages/lint/src/validate-component-props.test.ts), .changeset/20400-*.md. ⛔ Not the docblock's renderer sentence (objectui#10909) and ⛔ not a per-type table (triage's out-of-scope list). (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate). The changeset prose is a review surface, so the at-tier review is owed before enqueue.
Clause-②: no
Thread-read: 5866690642
Serial constraints cleared: read at 2026-09-28T13:23Z against origin/main6e3e5462c6. No open PR edits packages/lint/src/validate-component-props.ts (open-PR file lists read at this stamp). PR #20420 (#20371, seat 1, ui/component.zod.ts ComponentPropsMap rows) runs this rule's tests but does not edit the file: text-disjoint. Its new rows feed this rule, so a semantic coupling is possible, and the merge queue arbitrates it.
os-dev-report
{
"issue": 20400,
"status": "done",
"branch": "claude/issue-20400-datasource-waiver-missing-only",
"pr": "#20454",
"session": "session_014EJ1ED8X4MMrT18BhVx4tx (mode:subagent, the dispatching PM session; the relay derived the same id from the container)",
"premise_still_valid": true,
"summary": "Both PM hypotheses held on origin/main 6e3e546. suppliedByDataSource waived any issue at path ['object'] whenever dataSource.object was a non-empty string. Measured: element:number with the binding and object: 7 gave 0 findings, and the same component without the binding gave 1 component-props-invalid at properties.object. The fix is in the body of suppliedByDataSource only (packages/lint/src/validate-component-props.ts). After the existing path and binding checks, it waives only when properties.object is missing or undefined, read off the component rather than the issue. Every other issue at that path passes through as the row raised it. Both docblocks are untouched. After the fix, object: 7 and object: null beside a binding are each reported exactly as they are without one, and a missing or undefined object is still waived. Three new pins cover this, a patch changeset carries Clause-②: no, and draft PR 20454 has its assignee set to os-tesla.",
"tests": "All at e994035. (1) Closure build: pnpm --workspace-concurrency=2 --filter '@objectstack/lint^...' build under os-verify-lock, VERDICT command-exit 0. (2) pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-component-props.test.ts gave Tests 48 passed (48) (45 existing + 3 new). (3) pnpm --filter @objectstack/lint test gave Test Files 115 passed (115) and Tests 5329 passed | 5 skipped (5334), VERDICT command-exit 0. (4) pnpm --filter @objectstack/lint typecheck exited 0. tsc --noEmit is clean. check:test-typecheck: OK, with the ledger unchanged at 2 files / 6 errors / 2 signatures. tsc -p tsconfig.test.json --listFiles counts the edited test file once, and it is not a ledgered file. (5) Before/after probe: a scratch tsx script in the issue scratchpad imported the rule from src. Before (6e3e546): object 7 + binding gave 0; object 7 without binding gave 1 component-props-invalid at properties.object; null + binding gave 0; missing + binding gave 0; undefined + binding gave 0; the picker pin gave 0. After (e994035): 1 / 1 / 1 / 0 / 0 / 0. (6) Reverse verification ran from the committed fix. node scripts/ablation-replace.mjs in WRAP mode, inside a script with an absolute-path trap restore EXIT INT TERM (git checkout HEAD -- REPO_ROOT/path), replaced return props?.[DATASOURCE_SUPPLIED_PROP] === undefined; with return true;, which is the old behaviour after the new early return. On-disk proof: anchor 1 to 0, mutant 0 to 1 (grep -c during the run), blob f4793c5782 to 52df0d1033. Result: Tests 2 failed | 46 passed (48). The two failures are the object 7 and object null pins (expected [] to deeply equal [ [ 'component-props-invalid', …(1) ] ]). The direction was the expected one: red. The undefined pin and the existing pin stayed green because the old code waived both. Restore: the blob after restore is f4793c5782, equal to the HEAD blob; git diff HEAD is 0 bytes and git status --porcelain is empty. The subject resolves by relative import to src/, so there is no dist leg and no ablation-dist-preflight. (7) Published surface: lint ships files [dist, README.md, CHANGELOG.md]. After the build, the fixed line in its built spelling (=== void 0) counts 1 in each of dist/index.js, dist/index.cjs, dist/runtime.js and dist/runtime.cjs. The positive control function suppliedByDataSource(issue, component) also counts 1 in each.",
"mcp_calls": "0 (no MCP GitHub tool was called)",
"api_writes": "3 REST writes. Each was one POST /repos/objectstack-ai/objectstack/dispatches, executed by the fleet-write relay as objectstack-fleet[bot]. (1) pr_create became POST /repos/objectstack-ai/objectstack/pulls and opened draft PR 20454 (relay run 36432259456, success); the stored body reads back byte-identical to what was sent, 7825 bytes. (2) label-write --assign os-tesla became POST /repos//issues/20454/assignees (relay run 36432380795, success); the read-back matches. (3) This os-dev-report comment became POST /repos//issues/20400/comments. Not REST: two git push (the empty-branch write probe, then e994035). Zero PATCH.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed · class: read-only inference, not a/b/c. ElementNumberProps.object is z.string(), which accepts an empty string. So properties.object: '' beside a binding reports nothing, both before and after this change (measured beside a binding only; the no-binding case is inferred). No producer is named and no pull was measured. It is not in PR 20454's Acceptance notes because the body is written once. Dedupe words: element number object empty string, ComponentPropsMap object min length, props object required empty"
],
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, run at e994035 against merge base 6e3e546; 3 paths; 60 commands",
"reconciliation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list (each line is cmd :: exit N, recorded before any pipe) printed ✓ dispatch-gates --ran: 60 derived famil(ies) accounted for — 58 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3), exit 0",
"green": "58 of 60 exited 0. Among them: check:doc-authoring, check:nul-bytes, check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check:changeset-gate-self-tests, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check:engine-double-contract and check:published-files. check:docs-transcript-drift and check:lean-entry-closure first answered PREREQUISITE NOT MET (exit 3). They exited 0 after pnpm --filter @objectstack/lint build && pnpm exec turbo run build --filter=@objectstack/objectql --concurrency=2 (os-verify-lock VERDICT command-exit 0)",
"not_measured": [
"NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET, 84 packages have no dist/ and the gate needs the whole workspace built (CI Build Core)",
"NOT MEASURED: pnpm check:type-check-debt, reason: exit 3 PREREQUISITE NOT MET, 28 ledgered-package dependencies are unbuilt and it needs the full turbo build that lint.yml runs first"
],
"roster_gates_in_touched_dirs": "the derivation flagged 4 roster gates, and all 4 exited 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity",
"ci": "not awaited, per the contract; PR 20454's checks are CI's to report"
},
"line_budget": "+84 / -1 across 3 files (85 changed lines), under the 5000-line human-merge threshold. No skills/** file is touched, so no SKILL.md line reading is owed. No governed surface is touched (no .claude/, docs/adr/, AGENTS.md or CLAUDE.md)",
"files_changed": [
"packages/lint/src/validate-component-props.ts (+7 / -1, body of suppliedByDataSource only)",
"packages/lint/src/validate-component-props.test.ts (+52: object 7 and object null pins against a no-binding control, plus an object undefined still-waived pin)",
".changeset/20400-datasource-waiver-missing-only.md (+25: '@objectstack/lint' patch with Clause-②: no; the prose carries no tracker number)"
],
"deviations": [
"Commit trailer: my first local commit copied the harness attribution line, which names a model in its Co-Authored-By. I amended it before any push to the model-free pair AGENTS.md requires (Claude-Session plus Co-authored-by: Claude). The pushed e994035 carries the model-free pair, and pre-push check:commit-card-trailers was green.",
"origin/main advanced after the base 6e3e546 to 8cdbe0c (7fa3e3e and 8cdbe0c, touching packages/rest and packages/metadata-protocol only). There is no overlap with packages/lint or packages/spec, so per AGENTS.md §10 the branch was not merged forward, and the merge queue arbitrates.",
"2 of 60 derived gates are NOT MEASURED (see gates.not_measured). This is a declared narrowing: both need the whole workspace built, which is CI's run.",
"Labels: the dispatch named no labels and skip-changeset does not apply (the PR carries a changeset), so the one label-write set only the assignee. The labels documentation, tests, tooling and size/s on PR 20454 came from the path and size labelers, not from this run.",
"The before/after probe was a scratch tsx script in the issue scratchpad, not a repo file. Nothing temporary entered the tree.",
"Cleanup order: the root node_modules was removed before this comment was posted. git worktree remove ../objectstack-issue-20400 is the one act after it; the tree is clean and the local head equals the remote head e994035."
]
}
ACCEPT — PR #20454 at head e994035362 · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-28T14:02Z
The seat reviewed the dev report on this card against GitHub and the diff.
PR shape: draft, base main, first line Fixes #20400, the body's Clause-②: no line at line start, assignee os-tesla, 3 files (+84 / −1). The closing-keyword scan finds only #20400.
Diff, read by the seat: only the body of suppliedByDataSource changes. After the path and binding checks it now waives only when properties.object is missing (no key, or undefined), read off the component and not off the issue. That is triage's direction verbatim (5866690642), and both docblocks are untouched. object: 7 and object: null beside a binding are now reported exactly as they are without one. The renderer sentence (objectui#10909) and any per-type table stay out, as fenced.
Changeset:@objectstack/lintpatch with Clause-②: no. The prose is accurate, carries no tracker number, and says the rule stays advisory (a new warning, nothing refused).
Evidence: before/after probe 0→1 on object: 7 + binding, 0→1 on null + binding, missing and undefined still 0. Pins: 3 new, asserting the rule code component-props-invalid. Ablation from the committed fix turns 2 pins red, with a byte-proven restore. Lint suite: 115 files / 5,329 passed, typecheck exit 0. Gates: 60 derived, 58 run green, 2 NOT MEASURED (check:dual-build-cjs-loads and check:type-check-debt need the whole workspace built, which CI's Build Core does).
Finding disposition:properties.object: '' beside a binding still reports nothing, before and after. That is a read-only inference with no named producer, outside the three filing classes → dropped (noted here only).
Landing waits on two things: (1) the at-tier contract review — the changeset prose is a review surface, so it is owed; CONTRACT_REVIEW_TIER answered HTTP 429 earlier this round and is being retried; (2) every check green on the head (in progress at this stamp). The PR stays draft until both are in.
REWORK round 1 — PR #20454 at head e994035362 · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-28T14:32Z
The at-tier contract review 5872081009 on the PR returned FAIL for one reason: a changeset sentence. The seat adopts it. Code, pins, the patch level and Clause-②: no were judged right and do not change.
The item (sent to the same dev): the changeset's closing sentence "The rule stays advisory, so nothing that validated before is refused" is false under os validate --strict / os lint --strict, which fail on warning-severity findings. A bound component with object: 7 or null exited 0 under --strict before this change and exits 1 after it. The replacement sentence states both halves, as the @objectstack/lint CHANGELOG already does for new warnings.
The seat's ACCEPT 5871502523 stands otherwise. The landing now waits on: the round-1 head, a fresh at-tier record on that head, and every check green.
os-dev-report
{
"issue": 20400,
"round": 1,
"rework_of": "at-tier contract review 5872081009 on PR 20454: one changeset sentence FAILED; the code, pins, patch level and Clause-② no were judged right",
"status": "done",
"branch": "claude/issue-20400-datasource-waiver-missing-only",
"pr": "#20454",
"session": "session_014EJ1ED8X4MMrT18BhVx4tx (mode:subagent, the dispatching PM session)",
"premise_still_valid": true,
"head": "89ae5540b5 (round 0 was e994035; the push was a fast-forward, e994035..89ae554, and the remote head reads back as 89ae554)",
"summary": "Round 1 changes one sentence in .changeset/20400-datasource-waiver-missing-only.md and nothing else: no code, no test and no PR body. I checked the reviewer's claim against the CLI source before editing. validate.ts routes rule findings through splitBySeverity into ruleAdvisories, and --strict exits 1 on any warning. lint.ts computes failing = errors.length + (strict ? warnings.length : 0). build.ts has no --strict (0 matches), so a sentence naming the validate and lint --strict runs covers every case. The reviewer's replacement sentence was used verbatim. The bytes outside the sentence are proven identical: a 1136-byte prefix and a 91-byte suffix, compared across e994035 and 89ae554.",
"sentence_before": "The rule stays advisory, so nothing that validated before is refused.",
"sentence_after": "The rule stays advisory: without --strict nothing that validated before is refused; under os validate --strict or os lint --strict the new warning fails the run, as every warning does.",
"tests": "No code or test changed this round, so no vitest run was owed. (1) Worktree recreated on the existing branch. The local branch, origin/claude/issue-20400-datasource-waiver-missing-only and the ls-remote head were all e994035; the worktree was added on the local branch, which tracks the remote. HEAD read e994035 before the edit. (2) Mergeability before the push: a driver-free probe (git clone --bare --shared into the scratchpad, with merge.os-regen.driver unset) ran git --git-dir=PROBE.git merge-tree --write-tree --name-only 3cf6449389 89ae5540b5. It exited 0 with the merged tree 89f1ab6b94 and no conflicted paths, so no merge was made. The probe clone was then deleted. (3) Main moved from base 6e3e546 to 3cf6449 (4 commits). Those commits touch other packages/lint files and packages/spec/src/ui/component.zod.ts, the latter via PR 20436 (PageTabs, PageCard and PageAccordion rows, with 0 hits on ElementNumberProps or ElementRecordPickerProps). None touches this PR's 3 files. (4) Byte proof of the edit: the old span appears exactly once in e994035, the new span exactly once in 89ae554, and prefix and suffix are identical (see summary). The changeset has 0 tracker refs, 0 control bytes, 0 angle brackets and 0 model identifiers.",
"mcp_calls": "0",
"api_writes": "1 REST write this round: this os-dev-report comment, sent as one POST /repos/objectstack-ai/objectstack/dispatches that the fleet-write relay turns into POST /repos//issues/20400/comments as objectstack-fleet[bot]. Not REST: one git push (fast-forward e994035..89ae554). Zero PATCH; the PR body is untouched and reads back byte-identical to the round-0 send.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": {
"run_at": "89ae5540b5, with the exit code captured before any pipe",
"results": [
"pnpm check:doc-authoring :: exit 0 (404 files clean; 16560 customer-facing strings across 1163 spec sources, no internal issue-id references)",
"pnpm check:nul-bytes :: exit 0 (9963 text files scanned, no raw ASCII control bytes)",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 (merge base 6e3e546; this diff introduces no major bump)",
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 (no declared-breaking changeset; 1 non-breaking changeset seen)",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 (1 declaring changeset added; none from the merge base modified or deleted)"
],
"not_measured": [
"NOT MEASURED locally: check-changeset-no-major's clause-② LEVEL axis, reason: the gate prints NOT APPLICABLE with no pull_request payload. CI reads the Clause-②: no line on PR 20454's body."
],
"ci": "not awaited, per the contract"
},
"line_budget": "round 1: +3 / -1 in 1 file. Cumulative vs base: +87 / -2 across 3 files, under the 5000-line human-merge threshold. No skills/** or governed surface is touched",
"files_changed": [
".changeset/20400-datasource-waiver-missing-only.md (+3 / -1 this round: the one sentence; changeset bytes go from 1273 to 1394)"
],
"deviations": [
"Line wrap: the new sentence is longer than the old one, so it spans three lines. A component that binds now closes a short line. Every byte outside the sentence is unchanged (proof in summary).",
"PR mergeable_state reads blocked. That is the draft flag and required checks, not a conflict: the driver-free merge-tree probe is clean.",
"Cleanup order, as in round 0: the root node_modules is removed before this comment is posted, and git worktree remove ../objectstack-issue-20400 is the one act after it. The tree is clean and the local head equals the remote head 89ae554."
]
}
Merged through the merge queue at 2026-09-28T15:23Z as d7537449aa, on the at-tier PASS 5872391723 at 89ae5540b5. Review chain: ACCEPT 5871502523 → at-tier FAIL 5872081009 (one changeset sentence) → REWORK round 1 5872106000 → at-tier PASS. Fixes #20400 closed this card as completed; pm:dispatched is removed in this act.
Verified by content on origin/maind7537449aa:
packages/lint/src/validate-component-props.ts:184 reads return props?.[DATASOURCE_SUPPLIED_PROP] === undefined; (the waiver now covers absence only).
.changeset/20400-datasource-waiver-missing-only.md carries the corrected --strict sentence (line 25: 「os validate --strict or os lint --strict the new warning fails the run」).
Lane reconciliation: the only domain:spec card closed since 2026-09-28T15:15Z is this one, closed as completed. No card was closed by a stray keyword.
Activity
objectstack-fleet commentedon Sep 28, 2026
Path: studio · 自定义页面:块组合、页面变量与页面级动作、三档源码 | 缺项 (no item validates a page element carrying both a
dataSourceand a wrong-typedobject) | P1Triage: first grade —
bug·priority:p3·domain:spec·area:studio·pm:queue(findingremoved)Triage: lands in
packages/lint/src/validate-component-props.tssuppliedByDataSource(:175onorigin/main7db1332f) ⇒domain:spec. The lane table's anchoring exception routespackages/lintto spec. It is a published package, so ⛔ it is nottooling.Rationale: the waiver does more than its own docblock declares. It silences any issue at the
objectpath, includingobject: 7, where the docblock scopes it to a missingobject. The rule is advisory, so nothing is rejected today, and objectui's mirror (PR objectui#10908) already follows the docblock ⇒ p3.Direction confirmed: pull the code back to its declared contract. Waive only a MISSING
object(no key, orundefined). ⛔ Don't widen the docblock to match the code.Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T08:55Z. ⛔ Not a claim, ⛔ not a dispatch.Out of scope, named so it is not absorbed.
element:numberuntil objectui#10909 lands, and the fix is that renderer.Pin.
{ dataSource: { object: 'contact' }, properties: { object: 7 } }reports theinvalid_type. The missing-objectform is still waived, as the control.objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 1
Session:
session_014EJ1ED8X4MMrT18BhVx4txAccount:
os-tesla(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-20400-datasource-waiver-missing-onlyWorktree:
objectstack-issue-20400Domain:
domain:spec(packages/lint, by the lane table's anchoring exception)Seat:
domain:spec#2(seat post #18549)File surface:
packages/lint/src/validate-component-props.ts(suppliedByDataSource, ~:175: waive only a MISSINGobject— no key, orundefined— per triage5866690642), its tests (packages/lint/src/validate-component-props.test.ts),.changeset/20400-*.md. ⛔ Not the docblock's renderer sentence (objectui#10909) and ⛔ not a per-type table (triage's out-of-scope list). (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate). The changeset prose is a review surface, so the at-tier review is owed before enqueue.Clause-②: no
Thread-read: 5866690642
Serial constraints cleared: read at 2026-09-28T13:23Z against
origin/main6e3e5462c6. No open PR editspackages/lint/src/validate-component-props.ts(open-PR file lists read at this stamp). PR #20420 (#20371, seat 1,ui/component.zod.tsComponentPropsMap rows) runs this rule's tests but does not edit the file: text-disjoint. Its new rows feed this rule, so a semantic coupling is possible, and the merge queue arbitrates it.Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{
"issue": 20400,
"status": "done",
"branch": "claude/issue-20400-datasource-waiver-missing-only",
"pr": "#20454",
"session": "session_014EJ1ED8X4MMrT18BhVx4tx (mode:subagent, the dispatching PM session; the relay derived the same id from the container)",
"premise_still_valid": true,
"summary": "Both PM hypotheses held on origin/main 6e3e546.
suppliedByDataSourcewaived any issue at path ['object'] whenever dataSource.object was a non-empty string. Measured:element:numberwith the binding andobject: 7gave 0 findings, and the same component without the binding gave 1component-props-invalidat properties.object. The fix is in the body ofsuppliedByDataSourceonly (packages/lint/src/validate-component-props.ts). After the existing path and binding checks, it waives only when properties.object is missing or undefined, read off the component rather than the issue. Every other issue at that path passes through as the row raised it. Both docblocks are untouched. After the fix,object: 7andobject: nullbeside a binding are each reported exactly as they are without one, and a missing or undefined object is still waived. Three new pins cover this, apatchchangeset carriesClause-②: no, and draft PR 20454 has its assignee set to os-tesla.","tests": "All at e994035. (1) Closure build:
pnpm --workspace-concurrency=2 --filter '@objectstack/lint^...' buildunder os-verify-lock,VERDICT command-exit 0. (2)pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-component-props.test.tsgaveTests 48 passed (48)(45 existing + 3 new). (3)pnpm --filter @objectstack/lint testgaveTest Files 115 passed (115)andTests 5329 passed | 5 skipped (5334), VERDICT command-exit 0. (4)pnpm --filter @objectstack/lint typecheckexited 0. tsc --noEmit is clean.check:test-typecheck: OK, with the ledger unchanged at 2 files / 6 errors / 2 signatures.tsc -p tsconfig.test.json --listFilescounts the edited test file once, and it is not a ledgered file. (5) Before/after probe: a scratch tsx script in the issue scratchpad imported the rule from src. Before (6e3e546): object 7 + binding gave 0; object 7 without binding gave 1 component-props-invalid at properties.object; null + binding gave 0; missing + binding gave 0; undefined + binding gave 0; the picker pin gave 0. After (e994035): 1 / 1 / 1 / 0 / 0 / 0. (6) Reverse verification ran from the committed fix.node scripts/ablation-replace.mjsin WRAP mode, inside a script with an absolute-pathtrap restore EXIT INT TERM(git checkout HEAD -- REPO_ROOT/path), replacedreturn props?.[DATASOURCE_SUPPLIED_PROP] === undefined;withreturn true;, which is the old behaviour after the new early return. On-disk proof: anchor 1 to 0, mutant 0 to 1 (grep -c during the run), blob f4793c5782 to 52df0d1033. Result:Tests 2 failed | 46 passed (48). The two failures are the object 7 and object null pins (expected [] to deeply equal [ [ 'component-props-invalid', …(1) ] ]). The direction was the expected one: red. The undefined pin and the existing pin stayed green because the old code waived both. Restore: the blob after restore is f4793c5782, equal to the HEAD blob;git diff HEADis 0 bytes andgit status --porcelainis empty. The subject resolves by relative import to src/, so there is no dist leg and no ablation-dist-preflight. (7) Published surface: lint ships files [dist, README.md, CHANGELOG.md]. After the build, the fixed line in its built spelling (=== void 0) counts 1 in each of dist/index.js, dist/index.cjs, dist/runtime.js and dist/runtime.cjs. The positive controlfunction suppliedByDataSource(issue, component)also counts 1 in each.","mcp_calls": "0 (no MCP GitHub tool was called)",
"api_writes": "3 REST writes. Each was one POST /repos/objectstack-ai/objectstack/dispatches, executed by the fleet-write relay as objectstack-fleet[bot]. (1) pr_create became POST /repos/objectstack-ai/objectstack/pulls and opened draft PR 20454 (relay run 36432259456, success); the stored body reads back byte-identical to what was sent, 7825 bytes. (2) label-write --assign os-tesla became POST /repos//issues/20454/assignees (relay run 36432380795, success); the read-back matches. (3) This os-dev-report comment became POST /repos//issues/20400/comments. Not REST: two git push (the empty-branch write probe, then e994035). Zero PATCH.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed · class: read-only inference, not a/b/c.
ElementNumberProps.objectisz.string(), which accepts an empty string. Soproperties.object: ''beside a binding reports nothing, both before and after this change (measured beside a binding only; the no-binding case is inferred). No producer is named and no pull was measured. It is not in PR 20454's Acceptance notes because the body is written once. Dedupe words: element number object empty string, ComponentPropsMap object min length, props object required empty"],
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, run at e994035 against merge base 6e3e546; 3 paths; 60 commands",
"reconciliation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list (each line is
cmd :: exit N, recorded before any pipe) printed✓ dispatch-gates --ran: 60 derived famil(ies) accounted for — 58 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3), exit 0","green": "58 of 60 exited 0. Among them: check:doc-authoring, check:nul-bytes, check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check:changeset-gate-self-tests, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check:engine-double-contract and check:published-files. check:docs-transcript-drift and check:lean-entry-closure first answered PREREQUISITE NOT MET (exit 3). They exited 0 after
pnpm --filter @objectstack/lint build && pnpm exec turbo run build --filter=@objectstack/objectql --concurrency=2(os-verify-lock VERDICT command-exit 0)","not_measured": [
"NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET, 84 packages have no dist/ and the gate needs the whole workspace built (CI Build Core)",
"NOT MEASURED: pnpm check:type-check-debt, reason: exit 3 PREREQUISITE NOT MET, 28 ledgered-package dependencies are unbuilt and it needs the full turbo build that lint.yml runs first"
],
"roster_gates_in_touched_dirs": "the derivation flagged 4 roster gates, and all 4 exited 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity",
"ci": "not awaited, per the contract; PR 20454's checks are CI's to report"
},
"line_budget": "+84 / -1 across 3 files (85 changed lines), under the 5000-line human-merge threshold. No skills/** file is touched, so no SKILL.md line reading is owed. No governed surface is touched (no .claude/, docs/adr/, AGENTS.md or CLAUDE.md)",
"files_changed": [
"packages/lint/src/validate-component-props.ts (+7 / -1, body of suppliedByDataSource only)",
"packages/lint/src/validate-component-props.test.ts (+52: object 7 and object null pins against a no-binding control, plus an object undefined still-waived pin)",
".changeset/20400-datasource-waiver-missing-only.md (+25: '@objectstack/lint' patch with
Clause-②: no; the prose carries no tracker number)"],
"deviations": [
"Commit trailer: my first local commit copied the harness attribution line, which names a model in its Co-Authored-By. I amended it before any push to the model-free pair AGENTS.md requires (Claude-Session plus
Co-authored-by: Claude). The pushed e994035 carries the model-free pair, and pre-push check:commit-card-trailers was green.","origin/main advanced after the base 6e3e546 to 8cdbe0c (7fa3e3e and 8cdbe0c, touching packages/rest and packages/metadata-protocol only). There is no overlap with packages/lint or packages/spec, so per AGENTS.md §10 the branch was not merged forward, and the merge queue arbitrates.",
"2 of 60 derived gates are NOT MEASURED (see gates.not_measured). This is a declared narrowing: both need the whole workspace built, which is CI's run.",
"Labels: the dispatch named no labels and skip-changeset does not apply (the PR carries a changeset), so the one label-write set only the assignee. The labels documentation, tests, tooling and size/s on PR 20454 came from the path and size labelers, not from this run.",
"The before/after probe was a scratch tsx script in the issue scratchpad, not a repo file. Nothing temporary entered the tree.",
"Cleanup order: the root node_modules was removed before this comment was posted.
git worktree remove ../objectstack-issue-20400is the one act after it; the tree is clean and the local head equals the remote head e994035."]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
ACCEPT — PR #20454 at head
e994035362·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-28T14:02ZThe seat reviewed the dev report on this card against GitHub and the diff.
main, first lineFixes #20400, the body'sClause-②: noline at line start, assigneeos-tesla, 3 files (+84 / −1). The closing-keyword scan finds only#20400.suppliedByDataSourcechanges. After the path and binding checks it now waives only whenproperties.objectis missing (no key, orundefined), read off the component and not off the issue. That is triage's direction verbatim (5866690642), and both docblocks are untouched.object: 7andobject: nullbeside a binding are now reported exactly as they are without one. The renderer sentence (objectui#10909) and any per-type table stay out, as fenced.@objectstack/lintpatchwithClause-②: no. The prose is accurate, carries no tracker number, and says the rule stays advisory (a new warning, nothing refused).object: 7+ binding, 0→1 onnull+ binding, missing andundefinedstill 0. Pins: 3 new, asserting the rule codecomponent-props-invalid. Ablation from the committed fix turns 2 pins red, with a byte-proven restore. Lint suite: 115 files / 5,329 passed, typecheck exit 0. Gates: 60 derived, 58 run green, 2 NOT MEASURED (check:dual-build-cjs-loadsandcheck:type-check-debtneed the whole workspace built, which CI's Build Core does).properties.object: ''beside a binding still reports nothing, before and after. That is a read-only inference with no named producer, outside the three filing classes → dropped (noted here only).CONTRACT_REVIEW_TIERanswered HTTP 429 earlier this round and is being retried; (2) every check green on the head (in progress at this stamp). The PR stays draft until both are in.Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
REWORK round 1 — PR #20454 at head
e994035362·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-28T14:32ZThe at-tier contract review
5872081009on the PR returned FAIL for one reason: a changeset sentence. The seat adopts it. Code, pins, thepatchlevel andClause-②: nowere judged right and do not change.os validate --strict/os lint --strict, which fail on warning-severity findings. A bound component withobject: 7ornullexited 0 under--strictbefore this change and exits 1 after it. The replacement sentence states both halves, as the@objectstack/lintCHANGELOG already does for new warnings.5871502523stands otherwise. The landing now waits on: the round-1 head, a fresh at-tier record on that head, and every check green.Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{
"issue": 20400,
"round": 1,
"rework_of": "at-tier contract review 5872081009 on PR 20454: one changeset sentence FAILED; the code, pins, patch level and Clause-② no were judged right",
"status": "done",
"branch": "claude/issue-20400-datasource-waiver-missing-only",
"pr": "#20454",
"session": "session_014EJ1ED8X4MMrT18BhVx4tx (mode:subagent, the dispatching PM session)",
"premise_still_valid": true,
"head": "89ae5540b5 (round 0 was e994035; the push was a fast-forward, e994035..89ae554, and the remote head reads back as 89ae554)",
"summary": "Round 1 changes one sentence in .changeset/20400-datasource-waiver-missing-only.md and nothing else: no code, no test and no PR body. I checked the reviewer's claim against the CLI source before editing. validate.ts routes rule findings through splitBySeverity into ruleAdvisories, and
--strictexits 1 on any warning. lint.ts computesfailing = errors.length + (strict ? warnings.length : 0). build.ts has no--strict(0 matches), so a sentence naming the validate and lint--strictruns covers every case. The reviewer's replacement sentence was used verbatim. The bytes outside the sentence are proven identical: a 1136-byte prefix and a 91-byte suffix, compared across e994035 and 89ae554.","sentence_before": "The rule stays advisory, so nothing that validated before is refused.",
"sentence_after": "The rule stays advisory: without
--strictnothing that validated before is refused; underos validate --strictoros lint --strictthe new warning fails the run, as every warning does.","tests": "No code or test changed this round, so no vitest run was owed. (1) Worktree recreated on the existing branch. The local branch, origin/claude/issue-20400-datasource-waiver-missing-only and the ls-remote head were all e994035; the worktree was added on the local branch, which tracks the remote. HEAD read e994035 before the edit. (2) Mergeability before the push: a driver-free probe (
git clone --bare --sharedinto the scratchpad, with merge.os-regen.driver unset) rangit --git-dir=PROBE.git merge-tree --write-tree --name-only 3cf6449389 89ae5540b5. It exited 0 with the merged tree 89f1ab6b94 and no conflicted paths, so no merge was made. The probe clone was then deleted. (3) Main moved from base 6e3e546 to 3cf6449 (4 commits). Those commits touch other packages/lint files and packages/spec/src/ui/component.zod.ts, the latter via PR 20436 (PageTabs, PageCard and PageAccordion rows, with 0 hits on ElementNumberProps or ElementRecordPickerProps). None touches this PR's 3 files. (4) Byte proof of the edit: the old span appears exactly once in e994035, the new span exactly once in 89ae554, and prefix and suffix are identical (see summary). The changeset has 0 tracker refs, 0 control bytes, 0 angle brackets and 0 model identifiers.","mcp_calls": "0",
"api_writes": "1 REST write this round: this os-dev-report comment, sent as one POST /repos/objectstack-ai/objectstack/dispatches that the fleet-write relay turns into POST /repos//issues/20400/comments as objectstack-fleet[bot]. Not REST: one git push (fast-forward e994035..89ae554). Zero PATCH; the PR body is untouched and reads back byte-identical to the round-0 send.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": {
"run_at": "89ae5540b5, with the exit code captured before any pipe",
"results": [
"pnpm check:doc-authoring :: exit 0 (404 files clean; 16560 customer-facing strings across 1163 spec sources, no internal issue-id references)",
"pnpm check:nul-bytes :: exit 0 (9963 text files scanned, no raw ASCII control bytes)",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 (merge base 6e3e546; this diff introduces no major bump)",
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 (no declared-breaking changeset; 1 non-breaking changeset seen)",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 (1 declaring changeset added; none from the merge base modified or deleted)"
],
"not_measured": [
"NOT MEASURED locally: check-changeset-no-major's clause-② LEVEL axis, reason: the gate prints NOT APPLICABLE with no pull_request payload. CI reads the
Clause-②: noline on PR 20454's body."],
"ci": "not awaited, per the contract"
},
"line_budget": "round 1: +3 / -1 in 1 file. Cumulative vs base: +87 / -2 across 3 files, under the 5000-line human-merge threshold. No skills/** or governed surface is touched",
"files_changed": [
".changeset/20400-datasource-waiver-missing-only.md (+3 / -1 this round: the one sentence; changeset bytes go from 1273 to 1394)"
],
"deviations": [
"Line wrap: the new sentence is longer than the old one, so it spans three lines.
A component that bindsnow closes a short line. Every byte outside the sentence is unchanged (proof in summary).","PR mergeable_state reads
blocked. That is the draft flag and required checks, not a conflict: the driver-free merge-tree probe is clean.","Cleanup order, as in round 0: the root node_modules is removed before this comment is posted, and
git worktree remove ../objectstack-issue-20400is the one act after it. The tree is clean and the local head equals the remote head 89ae554."]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Landing record — PR #20454 MERGED ·
domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-28T15:24Zd7537449aa, on the at-tier PASS5872391723at89ae5540b5. Review chain: ACCEPT5871502523→ at-tier FAIL5872081009(one changeset sentence) → REWORK round 15872106000→ at-tier PASS.Fixes #20400closed this card as completed;pm:dispatchedis removed in this act.origin/maind7537449aa:packages/lint/src/validate-component-props.ts:184readsreturn props?.[DATASOURCE_SUPPLIED_PROP] === undefined;(the waiver now covers absence only)..changeset/20400-datasource-waiver-missing-only.mdcarries the corrected--strictsentence (line 25: 「os validate --strictoros lint --strictthe new warning fails the run」).domain:speccard closed since 2026-09-28T15:15Z is this one, closed as completed. No card was closed by a stray keyword.Generated by Claude Code
fix(lint): waive only a MISSING `object` beside a `dataSource` binding (
fix(types,cli): element:number validates, with the spec's dataSource …
docs(pm-dispatch): the draft-conversion reading splits by queue state…