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(react): SchemaRendererProvider's dataSource prop and SchemaRendererContextType are typed any — a string passed where the host adapter belongs raises nothing #7912
Found while covering packages/react/README.md for #5174 (batch 12, PR #7910). Filed unassigned and NOT fixed there — that card is scoped to the doc-snippet ledger, and this is a shipped type surface.
What
SchemaRendererProvider is the seam every ObjectUI app uses to inject its data source, and the prop that carries it is typed any on both sides — the provider's own props and the context type the whole tree reads back:
The built artifact carries the any straight through (packages/react/dist/context/SchemaRendererContext.d.ts:11 and :20), so no consumer can be told anything about it.
The shape is not unknown. @object-ui/types exports DataSource<T = any> (packages/types/src/data.ts, export interface DataSource), packages/app-shell/README.md already writes declare const myDataSource: DataSource; against exactly this seam, and useSettledSchema in this same package declares its parameter as DataSource<any> | null | undefined. So the type is known, published and already used one hop away — it is simply not declared here.
Measured
Probe planted on the SchemaRendererProvider example in packages/react/README.md, run under check:doc-snippets at c97bd02cb, direction predicted GREEN in writing before the run:
Result: exit 0, zero diagnostics. A bare string passed where the host's adapter belongs raises nothing — in a block that is now compiled, --strict, against the built types. Handing this prop a string is not a hypothetical mistake: it is the shape of a config value read from the wrong place, and it fails at runtime on the first find().
The batch-12 PR states this as the published bound of that document's new coverage rather than letting the green imply more than it checks.
Why it matters here specifically
This is the same defect class as #7483 (ObjectView's props surface typed any in @object-ui/app-shell), on a different and arguably more central surface: ObjectView is one route-level view, whereas SchemaRendererProvider is the injection point for the entire renderer tree, and useSchemaContext() hands the any to every consumer that reads it back. An AI author writing a host integration gets no signal at all from the compiler about what belongs on this key.
Contract-first (AGENTS.md #0.1, #6 "type safety over magic") points one way here: declare the published DataSource contract at the seam rather than widening consumers around it.
Not done here, on purpose
Narrowing dataSource: any to DataSource is a public-contract change on @object-ui/react, and it will red every internal caller that passes an adapter this repo has not typed. That is a scoped piece of work with its own blast radius, not a rider on a documentation-ledger batch. The measurement is recorded; the decision is not taken.
This is the same defect class as #7483 (ObjectView's props surface typed any), on a different and arguably more central surface: ObjectView is one route-level view, whereas SchemaRendererProvider is the injection point for the entire renderer tree, and useSchemaContext() hands the any to every consumer that reads it back.
Narrowing dataSource: any to DataSource is a public-contract change on @object-ui/react, and it will red every internal caller that passes an adapter this repo has not typed. That is a scoped piece of work with its own blast radius, not a rider on a documentation-ledger batch. The measurement is recorded; the decision is not taken.
需要裁决者答的一句话:SchemaRendererProvider.dataSource 应当声明为已发布的 DataSource 契约,还是保持 any 并明写「宿主适配器的形状不由 TypeScript 守」?
⚠️裁决前必须补的一项读数(本席与卡片都没做):把 dataSource: any 收成 DataSource 会红掉多少个内部调用点。 卡片说「every internal caller that passes an adapter this repo has not typed」——那个数字没人量过,而它直接决定这次收窄的价钱。⇒ 这是取卡人的第一步。
⚠️第二条:SchemaRendererContextType.dataSource(①)与 Provider props(②)可以分开处理——先收窄 ① 会影响每一个 useSchemaContext() 的读回点,先收窄 ② 只影响注入点。⇒ 两者的爆炸半径不同,请分别估。
Ruling recorded — declare the published DataSource contract at the seam (director seat, decision batch #68, 2026-09-07)
Maintainer reply, verbatim: 「同意」 (all five batch #68 recommendations adopted).
Ruling.SchemaRendererProvider's dataSource prop andSchemaRendererContextType.dataSource are typed DataSource (from @object-ui/types), not any. "Keep any and document that the host adapter's shape is not TypeScript's job" is refused: it contradicts AGENTS.md #0.1 / #6 on the most central seam of the renderer tree.
Execution notes.
Measure first: count the internal call sites that go red when each of the two anys is narrowed (the context type reaches every useSchemaContext() reader; the prop reaches only injection points). Record both numbers on this card before the PR; if the context narrowing is large, land it in the same PR anyway — a half-typed seam is the thing this ruling ends — but say what it cost.
Fix the red call sites by typing the adapters, never by casting to any at the seam.
Published TS accept set narrows (@object-ui/react): Clause-② yes, needs:contract-review, changeset stating the type change.
Pin: the planted probe from this card (dataSource={'not-an-adapter'}) must fail check:doc-snippets.
The ruling:SchemaRendererProvider's dataSource prop andSchemaRendererContextType.dataSource are typed DataSource (from @object-ui/types), ⛔ not any. "Keep any and document that the host adapter's shape is not TypeScript's job" was refused — it contradicts AGENTS.md #0.1 / #6 on the most central seam of the renderer tree.
⭐ Execution note 1 is your FIRST step, and it is a measurement nobody has taken
Measure first: count the internal call sites that go red when each of the two anys is narrowed … Record both numbers on this card before the PR.
⚠️ The two have different blast radii and must be estimated separately:
① SchemaRendererContextType.dataSource reaches every useSchemaContext() reader — this any is the one that infects the whole tree.
② the Provider's own props reaches only the injection points.
⭐ If the context narrowing turns out large, land it in the same PR anyway — the ruling says so in as many words: "a half-typed seam is the thing this ruling ends — but say what it cost."
⛔ Execution notes 2–5, quoted because each is a fence
⛔ Fix red call sites by typing the adapters, never by casting to any at the seam. A cast at the seam re-creates the defect one line lower.
The published TS accept set narrows on @object-ui/react ⇒ Clause-② yes, needs:contract-review, and a changeset stating the type change. ⚠️ 41 packages sit in ONE fixed group ⇒ major is unavailable; breaking ships as minor with a **BREAKING** carrier. Shape it after objectui#8604 / objectui#9187 (FROM/TO).
⭐ Pin: the planted probe from the card must FAIL check:doc-snippets — dataSource={adapter} → dataSource={'not-an-adapter'} on the SchemaRendererProvider example in packages/react/README.md. ⚠️ Today that probe exits 0 with zero diagnostics in a block already compiled --strict against the built types: the gate is running and has nothing to say. Your pin is that it starts having something to say.
objectui#7483 (ObjectView props typed any) follows the same direction — ⛔ do not fix it here, but note there when this lands.
⭐ A live, freshly measured input the card does not have
PR objectui#9289 (card objectui#9197) just landed the sibling repair: RecordContextValue.dataSource was declared string while every producer passed an adapter, and it is now DataSource. ⭐ That seat measured something directly useful to you: five as any casts in @object-ui/fields read SchemaRendererContext, not RecordContext — they are redundant against any today and are exactly the consumer face this card types. ⇒ they are part of your red-call-site count, and they were deliberately left for you. ⚠️Key it on the BINDING, not the variable name. That same seat found triage's cast list keyed on the identifier ctx, giving a count that agreed with the real one by coincidence while overlapping on only six of eleven.
⚠️ In flight — check before you edit
PR objectui#9289 touches packages/react/src/context/RecordContext.tsx and may still be in the merge queue when you start. Your file is SchemaRendererContext.tsx — a different file in the same directory. ⛔ Confirm that rather than assume it. Also live: objectui#8021 (docs guide) and objectui#7561 (packages/components/src/custom/filter-builder.tsx).
⛔ Never skip, disable or quarantine a test. ⛔ Never force-push or rewrite pushed history.
⚠️ There is no npm script matching no-major. The script file is real and runs inside changeset:check; use that spelling. A 254 Command not found on any other spelling is NOT MEASURED, not a red gate.
objectui has noscripts/pm/dispatch-gates.mjs; derive the gate family by hand from package.json + .github/workflows/.
⚠️A package-scoped test run is not the blast radius (objectui#9273) — and on this card the blast radius is the point. Run the union of every package whose call sites you touched, plus examples/schema-catalog/.
⚠️Install/build first. A missing node_modules makes everyrequire.resolve fail identically — instrument failure, not a negative answer. Read verdicts from the verify-lock wrapper's VERDICT line, never a bare $?. ⚠️ The shared lock has been heavily contended today; park the slot and report NOT MEASURED rather than running unlocked.
⚠️Carrier rule: hang needs:contract-review on both limbs in one stroke. ⛔ Never clear either limb; clearing is the PM's. (REST label POST needs Content-Type: application/json, else 415 — ⛔ not a permission failure.)
⛔ Do not put any model identifier into any pushed artifact. ⭐ This repo's history is currently inconsistent on it — one commit landed today carrying one — so ⛔ do not copy a neighbouring commit's trailer; use the model-free form. ⛔ Card relations belong in the PR body, not the commit message.
⭐ The two instrument standards this seat holds
Write the pin BEFORE you touch the code — the "unmodified" arm is then the real base tree. ⭐ Here that is unusually clean: the doc-snippet probe already fails to fail on today's tree, so plant it first and record the green as the base reading.
Every control must be able to fire.⚠️ A type-level pin's characteristic failure is the harness degrading the types to any, which silently inverts every row while looking perfect. Carry controls that prove resolution is real — a misspelled key must be refused, and DataSource's required members must be visible.
{
"issue": 7912,
"status": "done",
"branch": "claude/issue-7912-datasource-contract",
"pr": "#9310",
"premise_still_valid": true,
"summary": "Both anys at the renderer seam are now the published DataSource contract, spelled DataSource | null | undefined — the spelling useSettledSchema already used one hop away, so the three states a host really has (an adapter, or one of the two absences every reader already guards for) stay expressible while a string, an empty object, a data bag and a partial adapter are all refused. Execution note 1's measurement was taken FIRST and posted on the card before the PR (comment 5647354308): the context type costs 7 diagnostics at 7 line positions in 5 files across 4 packages, the provider props cost 52 at 52 positions in 27 files across 11 packages — the REVERSE of the ruling's prediction by 7.4x, because readers already guarded while injection points are overwhelmingly test doubles that were never complete adapters. Every red site is repaired by typing (a capability type guard for getClient, the published contract for ObjectGallery's hand-rolled { find } stand-in, a null-to-undefined collapse where a widget prop declares one spelling, the absent adapter itself instead of a truthy empty-object stand-in in the react page) and never by a cast at the seam; the five redundant as any context reads in @object-ui/fields are gone, along with LookupField's local re-declaration of the imported context as a Context of any that laundered its dataSource read while looking typed. Two runtime behaviours move, both toward 'no adapter' and both closer to what the surrounding code already intended; test doubles keep every injected value byte-for-byte and mark the crossing instead, 65 sites in 33 files.",
"tests": "All heavy runs through ../objectstack/scripts/pm/os-verify-lock.sh (slot os-dev-7912); verdicts read from its VERDICT line, never a bare exit status. TYPE-CHECK, the union the blast radius demands: turbo run type-check --filter='...@object-ui/react' --filter='!@object-ui/site' --concurrency=2 --continue = 33 packages, builds included, both tiers each -> 66 tasks successful, 66 total, 0 diagnostics (VERDICT command-exit 0). Baseline on the untouched base tree was the same shape with 0 diagnostics, which is what makes every variant reading attributable. TESTS, repo-root form, union of every package whose call sites this touches plus examples/schema-catalog (objectui#9273): run A pnpm exec vitest run packages/react/ packages/fields/ packages/plugin-list/ packages/plugin-calendar/ -> 350 files / 4882 tests passed; run B ... packages/components/ packages/plugin-gantt/ packages/plugin-kanban/ packages/plugin-charts/ packages/plugin-dashboard/ packages/plugin-detail/ examples/schema-catalog/ -> 770 files / 8845 tests passed; both VERDICT command-exit 0. Union 1120 files, 13727 tests, zero failures. GATES (hand-derived from package.json + .github/workflows; this repo has no scripts/pm/dispatch-gates.mjs), all exit 0: check:doc-snippets, check:doc-types, check:doc-fences, check:doc-example-ids, check:readme-exports, check:new-line-citations, check:phantom-deps, check:unused-deps, check:self-import, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:element-data-source-declaration, check:unreferenced-sources, check:lockfile-integrity, check:control-bytes, check:changeset-claims, scripts/check-changeset-presence.mjs, and pnpm changeset:check (the real spelling; there is no no-major script, the file runs inside that one). Lint for the 10 touched packages + the root task: 11 tasks successful, exit 0; pnpm lint:coverage 46/46 packages, 0 outstanding errors. Clause-2 carrier pair: PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9310 -> EXIT 0, 'the clause-2 declaration is readable in the fixed spelling and both carriers agree'. PIN + ABLATION, both legs at the final commit c3e363b: (1) doc-snippet probe — planted FIRST on the untouched base tree (base 243fc83, dist built): exit 0, 646/646 blocks judged, 0 failed, zero diagnostics; same probe on this branch: exit 1 with packages/react/README.md:110:3 TS2322 'Type string is not assignable to type DataSource'. The gate was running and had nothing to say; it now says it. Probe restored byte-identically (git hash-object equals the HEAD blob), not committed. (2) type pin reverse verification — direction predicted in writing BEFORE the run (exactly the 5 BREAKING rows flip, all 3 CONTROLs must stay refused); both declarations ablated back to any on the committed tree with the mutation proved ON DISK (typed declarations 2 -> 0, any declarations 0 -> 2, blob hash changed) under a trap restore; result Tests 5 failed | 9 passed (14) — exactly the 5 BREAKING rows, all 3 controls still refused, so the flip is attributable to the declaration and not to the harness degrading types to any. Restored byte-identically; pin green 14/14 at HEAD. No dist rebuild was needed for the pin because it resolves the context by SOURCE path plus the repo's paths mapping, not through exports. NOT MEASURED, and honestly so: the first ablation attempt exited 1 from --reporter=basic, which vitest 4 does not have — a harness startup error, re-run with the default reporter for the reading above; under variant 1 the react and plugin-list TEST tiers were not measured at all (a package's type-check is tsc --noEmit && tsc -p tsconfig.test.json and the first leg failing short-circuits the second), and variant 1 additionally produced 6 TS7016 diagnostics in 3 packages that are a build cascade from plugin-list not emitting its own .d.ts, excluded from the 7 as not-call-sites. The rest of the check:* farm and the repo-wide ESLint scan are CI's runs, as is full-repo pnpm test; CI convergence and the ready/queue flip are left to the PM.",
"mcp_calls": "0 — the whole run used the container's repo-scoped REST channel (probed green at the start: GET /issues/7912 -> HTTP 200) plus git; no MCP GitHub tool was called for reads, writes, labels, the PR, or the dedup listing",
"open_questions": [],
"out_of_scope_findings": [
"filed as #9308: the seam's key carries a SECOND, undeclared meaning — the data scope that bind paths and data.* predicates resolve against (useDataScope walks the injected value; SchemaRenderer binds it as data; five production widgets read it that way, and reportAdapterOnlyDataPredicate exists to warn when it does not resolve). Declaring the adapter meaning prices the bag meaning out for any conformant host: that is the one thing this narrowing costs, it is measured (65 marked sites / 33 files), and the choice between giving the data scope its own key, retiring it, or declaring a union is a second published-surface decision NOT taken here",
"filed as #9309: ObjectGallerySchema.filter is typed unknown while its own docblock says it is forwarded verbatim as $filter, and it is the one view schema of six in @object-ui/types that is not any[] — it is why the single $filter forward in ObjectGallery had to be asserted rather than declared, and that assertion carries a comment naming the card",
"noted, not filed: useCascadingOptions.ts:45 keeps its binding-level as any on purpose — its only reads are the formValues/data tail objectui#7206 explicitly leaves undecided, so narrowing the binding would red exactly those two reads. Successor: objectui#7206",
"noted, not filed: FieldWidgetProps.dataSource is declared unknown in @object-ui/fields' own widgets/types.ts with a written rationale, and it is now the ONLY thing laundering the merged value at four of the five repaired read sites (the context leg is typed and cast-free at all five, and each of the four names that declaration in a comment). Successor: whoever takes #9308, which is the same one-key-two-meanings family",
"noted, not filed: SchemaRenderer.tsx's reader-side empty-object stand-in (the value that feeds the expression scope when no adapter is bound) is unchanged and was never red — it substitutes downstream of the seam rather than injecting through it. Successor: #9308",
"noted, not filed: ObjectGallery's envelope-sniffing block now sits under a real QueryResult declaration whose only required member is data, while two of its three branches read UNDECLARED envelopes (records, a bare array). Every branch is kept and the one widening is stated in place, because whether any adapter really answers with those two is a question about the adapters. Successor: none — no queued PR touches that block",
"in-flight overlap, not a finding: PR #9144 touches packages/react/src/tests/SchemaRenderer.predicateEnvelopeConfigBag.test.tsx, which this PR also touches (a note block plus one as unknown as DataSource on the context value). The union is mechanical and is written out in the PR body; if the queue rebuilds #9144 after this, its new provider-element lines need the same cast"
]
}
{
"issue": 7912,
"status": "blocked",
"branch": "claude/issue-7912-datasource-contract",
"pr": "#9310",
"premise_still_valid": true,
"summary": "The card's implementation is complete, verified and pushed (head 64c05fd); blocked names ONE outstanding item that this seat is fenced from touching, not an incomplete implementation. RED 1 ("Build Docs") is fixed: apps/site/app/components/LiveSplitDemo.tsx handed the provider { dataSource: {} }, the same truthy empty-object stand-in already corrected in the react page, and it now injects the absent adapter itself — every preset that component renders is a static schema, the object-bound demos inject the docs gallery adapter from their own files. The exclusion that hid it is gone: apps/site has its own type-check script and IS a dependent of @object-ui/react, so --filter='!@object-ui/site' was the load-bearing narrowing, and the union now runs without it (68/68, and @object-ui/site:type-check is a cache miss that really executed). RED 2 ("Skill Example Check") is reproduced verbatim and diagnosed, and is NOT repaired here per the dispatch's fence: the one failing fence is skills/objectui/guides/data-integration.md:304:26 TS18049 'dataSource' is possibly 'null' or 'undefined' — a marked example that destructures dataSource from useSchemaContext() and calls .find with no guard. It is a TRUE positive: a provider with no adapter bound is a real state (this PR's own two repairs are instances of it) and useSchemaContext() throws only when there is no PROVIDER, so that guidance, copied into a plugin on such a surface, throws at the first call. The repair is one line inside the fence (if (!dataSource) return [];), it lands in published skills/**, and check-governed-queue-guard.mjs --test confirms that adding that single file turns this NOT-GOVERNED PR into one governing the WHOLE pull request — draft-parked and needing an authorized APPROVED review. Routing it to the skills seat is the one thing only the PM can do.",
"tests": "Everything re-run at the new head before pushing; nothing rests on the previous 66/68. BUILD DOCS, the gate red 1 lives in: turbo run build --filter='@object-ui/site' --concurrency=2 (plus the skill-example closure in the same locked run) -> 35 tasks successful, 35 total, VERDICT command-exit 0, with @object-ui/site:build logged as 'cache miss, executing' running the real next build — so it is a measurement, not a restored cache. CORRECTED UNION: turbo run type-check --filter='...@object-ui/react' --concurrency=2 --continue with NO site exclusion -> 68 tasks successful, 68 total, 0 diagnostics (VERDICT command-exit 0); @object-ui/site:type-check also 'cache miss, executing' (fumadocs-mdx && next typegen && tsc --noEmit). That is the instrument correction: red 1 would have surfaced locally under it. SKILL EXAMPLES, the gate red 2 lives in: node scripts/check-skill-examples.mjs --self-test -> exit 0, 59 cases pass (so the harness itself is sound, and a permanent-green harness is ruled out); node scripts/check-skill-examples.mjs -> exit 1 with exactly one finding, [semantic] skills/objectui/guides/data-integration.md:304:26 TS18049: 'dataSource' is possibly 'null' or 'undefined' and 'Semantic phase: 14 of 14 ts fence(s) judged, 1 failed'. STILL GREEN at the new head: check:doc-snippets -> exit 0, 646/646 blocks judged, 0 failed (its first run in the rebuilt worktree exited 2, PRECONDITION NOT MET / unbuilt packages — NOT MEASURED, not a documentation defect; re-run after building its own --build-filter closure); the type pin -> 14/14 passed. GOVERNANCE READING, quoted because it is the routing evidence: node scripts/check-governed-queue-guard.mjs --test says this diff is 'NOT GOVERNED - an ordinary pull request', and the same diff plus skills/objectui/guides/data-integration.md says 'One governed path governs the WHOLE pull request' with the draft-park and APPROVED-review requirement. No test run was needed for the site fix: find apps/site -name '*.test.*' returns nothing, so the earlier 1120-file / 13727-test union is unchanged by it. The PR body now states both gates as measured and what each cost; its readback shows the only mutation was GitHub appending a second attribution footer (+58 bytes, additive, nothing eaten), which AGENTS.md records as expected on PATCH and explicitly says not to 'repair'.",
"mcp_calls": "0 — still zero for the whole run; this round used REST (PR readback, PATCH, comments) and git only",
"open_questions": [],
"out_of_scope_findings": [
"ROUTED, not filed and not repaired: skills/objectui/guides/data-integration.md:304 — the marked example under 'Via DataSource methods (in plugin code)' calls .find on the context adapter with no guard, which the narrowed contract now refuses (TS18049). Minimal repair is one line inside that fence; the other option the gate names (dropping the os:check marker) would be the wrong one, because the example is meant to compile and only needs the guard the contract now requires. Published skills/** is a governed surface and the dispatch fenced this seat from editing it, so it is handed back for routing to the skills seat. Until it lands, 'Skill Example Check' stays red on this PR by design",
"instrument finding, worth carrying into the next dispatch: a declared narrowing of the verification union can still be the load-bearing one. --filter='!@object-ui/site' looked like an exclusion of a docs app with no type surface; apps/site in fact declares type-check AND is a dependent of the package under change, so the exclusion removed the exact surface that failed. The cheap check before trusting any such exclusion is a membership read (pnpm -r --filter '...PKG' list --depth -1, spelling PKG as a word) plus a look at the excluded package's own scripts",
"unchanged from the first report and still standing: #9308 (the one-key-two-meanings finding), #9309 (ObjectGallerySchema.filter typed unknown), the four noted-not-filed items, and the #9144 overlap note in the PR body"
]
}
{
"issue": 7912,
"status": "needs_decision",
"branch": "claude/issue-7912-datasource-contract",
"pr": "#9310",
"premise_still_valid": true,
"summary": "One validated push, 373c1c8 to e2c31aa. The BLOCKING red is green: Type Check (a required context) failed only inside packages/components, on src/__tests__/guide-schema-rendering-data-context-8021.test.tsx(111,31) error TS2740 - objectui#8021's test injects a plain scope bag as the provider's dataSource, on purpose, because its legs A/C/D measure the expression data root. That file landed on main AFTER this branch's own sweep and arrived with the 373c1c8 merge, so it is the one injection site the sweep could not have marked; it now carries the same explicit as unknown as DataSource crossing and per-file note as the other 33 files here (65 marked sites becomes 66, in 34 files). Nothing widened, nothing cast at the seam, nothing skipped: the declaration is untouched, the injected value is byte-for-byte what it was, all six assertions unchanged and passing. Doc Snippet Type Check is NOT fixable inside this PR and that is measured, not assumed - see open_questions. Skill Example Check is unchanged and still exactly objectui#9311's one fence; skills/** untouched. Re-measured the required set on GET /repos/objectstack-ai/objectui/rules/branches/main at 2026-09-13: Lint, Type Check, Build & E2E, Test (shard 1/4) through (4/4), Build Docs, Changeset Declaration - the two remaining reds are NOT required contexts, so they are reported findings rather than queue blockers. Membership read done as asked: turbo ls --filter '...@object-ui/react' names 33 packages and @object-ui/site IS one of them (it has a real type-check script: fumadocs-mdx, next typegen, tsc --noEmit), so the previous round's declared exclusion is now closed and measured green. PR body updated with the whole round; file face confirmed disjoint from #9338/#9339/#9343/#9138/#9144/#9250 (none touches this file). PR left draft, not armed.",
"tests": "All heavy runs through ../objectstack/scripts/pm/os-verify-lock.sh; verdicts are its VERDICT line, never a bare $?. REPRODUCTION before the fix - turbo run type-check --filter=@object-ui/components: "src/tests/guide-schema-rendering-data-context-8021.test.tsx(111,31): error TS2740: Type '{ user: { name: string; role: string; }; stats: { totalUsers: number; }; }' is missing the following properties from type 'DataSource OF any': find, findOne, create, update, and 2 more." → "os-verify-lock: VERDICT command-exit 2". AFTER the fix, same command: "Tasks: 9 successful, 9 total" → "VERDICT command-exit 0". WHOLE-WORKSPACE turbo run type-check --concurrency=2 (what the required Type Check job runs): "Tasks: 81 successful, 81 total" → "VERDICT command-exit 0", zero error TS lines in the log. @object-ui/site alone (the previously excluded member): "Tasks: 31 successful, 31 total" → "VERDICT command-exit 0". TESTS: edited file alone "Test Files 1 passed (1) / Tests 6 passed (6)" → VERDICT command-exit 0; its package (the blast radius of a test-only edit) vitest run packages/components/: "Test Files 270 passed (270) / Tests 2603 passed (2603)" → "VERDICT command-exit 0". LINT: turbo run lint --filter=@object-ui/components → "956 problems (0 errors, 956 warnings)", "Tasks: 2 successful, 2 total", exit 0; the edited file itself 0 errors / 0 warnings (eslint --format json, 1 file). Narrowing declared: 1 of that package's 485 linted files moved; this repo's root lint is turbo run lint (per-package eslint .), and no type-aware linting is configured (no project:/projectService in eslint.config.js), so this diff cannot move the verdict on a file it did not touch; the repo-wide scan is CI's. DOC SNIPPETS: built the gate's own closure first (--build-filter → 34 packages, "Tasks: 35 successful, 35 total", VERDICT command-exit 0) because an unbuilt tree gives exit 2 = PRECONDITION NOT MET = NOT MEASURED. Gate then: exit 1, "Semantic phase: 649 of 649 block(s) judged, 3 failed" at content/docs/guide/schema-rendering.md:99:29, :434:29 and packages/react/README.md:73:29, all TS2740 (a data bag where the adapter belongs). Controls in the same run all fired - resolution resolved @object-ui/types to the built dist/index.d.ts, sentinel produced TS2305, positive produced 0 - so these are readings, not a degraded harness. Identical before and after my commit (my diff touches no document). Baseline for attribution: this branch was 646/646 judged, 0 failed at c3e363b; the three blocks arrived with the main merge. SKILL EXAMPLES: exit 1, "Semantic phase: 14 of 14 ts fence(s) judged, 1 failed", the single skills/objectui/guides/data-integration.md:304:26 TS18049 - byte-for-byte objectui#9311's. GATES, all exit 0: check:control-bytes ("scanned 7526 tracked text file(s)"), check:phantom-deps, check:self-import, check:test-path-roots, check:new-line-citations ("0 new citation(s)"), check:vi-mock-specifiers, check:doc-fences, check:doc-example-ids, check:changeset-claims. Control-byte self-scan of the edited file with grep -naP: no hits. NO ABLATION this round: the diff adds a type assertion to one test file and introduces no new guard to prove can fail; the declaration's own reverse verification is the previous round's, at c3e363b. NOT MEASURED, by design: the rest of the check:* farm, the repo-wide ESLint scan and full-repo pnpm test are CI's runs; CI convergence is the PM's.",
"mcp_calls": "4 - 3 search_issues (dedupe/related-card lookups; the 3rd returned 'API rate limit already exceeded', so it is NOT a reading) and 1 issue_write create for objectui#9346. Every issue/PR read, the PR body PATCH and the report comment went through REST, which answers 200 in this container for reads and writes; REST /search/issues answers 403 here, which is why the dedupe search had to switch channel to MCP - declared.",
"open_questions": [
{
"question": "Doc Snippet Type Check stays red on three fences and I could not repair any of them without changing what the page teaches. All three are objectui#8021's, all three arrived with the 373c1c8 merge, and all three teach the SECOND meaning of this key - the page's own values published through dataSource and read back under the data. path prefix - which objectui#9308 is open to decide. Filed with the full measurement as objectui#9346. How should it be closed out?",
"options": [
"A - rule objectui#9308 first (give the data scope its own key on the provider, or retire meaning 2 at this seam), then one documentation pass moves the three fences AND objectui#8021's pin together. This is the only route that leaves the pages true.",
"B - land PR objectui#9310 with Doc Snippet Type Check red, since it is NOT a required context (re-measured 2026-09-13) and objectui#9346 carries the reading. Costs a red context on every later PR until A happens.",
"C - cast inside the published fences to get the gate green. REFUSED here: execution note 2 says a cast at the seam re-creates the defect one line lower, and these are teaching surfaces - a guide showing the reader how to defeat the contract this PR establishes is worse than a red context.",
"D - declare the three blocks in the gate's UNGATED ledger. REFUSED here: the gate's own header allows a block to be repaired or declared with a written reason, and the only available reason is 'the page teaches an off-contract wiring', which is a quarantine."
],
"recommendation": "B now, A next. B unblocks the queue today at a known, named cost, because the context is not required and objectui#9346 holds the measurement. A is the real repair and it is not a documentation edit: content/docs/guide/schema-rendering.md is PINNED to the current teaching by objectui#8021's own leg A, which reads the Data Context fence off the page, branches on whether it carries a provider dataSource= attribute, and asserts the Accessing Data fence renders 'Welcome, John!' - remove the attribute and it reads 'Welcome, !'; move the expression off the data. path prefix and it prints raw source. So moving the page off meaning 2 moves a landed card's pin, which is exactly why it needs objectui#9308's ruling rather than a seat's judgement. C and D are listed only to be refused explicitly."
}
],
"out_of_scope_findings": [
"filed as #9346: objectui#8021's two repaired teaching surfaces inject a plain data bag as the provider's dataSource - three fences that stopped compiling under the objectui#7912 narrowing (blocked on objectui#9308; class (a), an example that fails when copied verbatim)",
"noted, not filed: the PR body now carries four attribution footers stacked at its end, accreted by successive edits (two bare, two session-URL). Cosmetic, no mechanism reads them; successor: the next seat to edit this PR body, and I left them untouched rather than risk a rewrite of a correct body",
"noted, not filed: pnpm exec eslint --no-inline-config . inside packages/components reports 33 errors across 30 files (all react-hooks/static-components in test files whose inline eslint-disable comments that flag ignores). This repo's real lint is turbo run lint = per-package eslint . WITH inline config, which is 0 errors - so this is an artefact of the objectstack root-lint spelling applied to objectui, not a defect here. Recorded so the next seat does not read it as a red. Successor: none",
"unchanged from the previous rounds and still standing: objectui#9308 (one key, two meanings - the decision the above waits on), objectui#9311 (the skills fence), objectui#9297, objectui#9309"
]
}
PR objectui#9310 limb, then this card's limb, seconds apart
The published accept set narrows: SchemaRendererContextType.dataSource and SchemaRendererProvider's own prop go from any to DataSource | null | undefined, so a string, {}, a data bag and a partial adapter are refused where they were accepted. minor across ten packages with the note leading **BREAKING** and a FROM/TO block — major is unavailable in this repo's single fixed group.
Re-verified on the merged head by this seat rather than quoted from the body: turbo run type-check --concurrency=2 over the whole workspace, no filter, Tasks: 81 successful, 81 total, VERDICT command-exit 0.
Two reds ruled, not waived
Doc Snippet Type Check — three fences teaching the second meaning of dataSource. Option B: lands red, at a named cost, until objectui#9308 is ruled. objectui#9346 carries the measurement and objectui#9308's ruling is the removal trigger. Options C (cast in a published fence) and D (the gate's ledger) are refused in the review with the gate's own text; option A is the real repair and is ⛔ not this seat's — objectui#9308 is needs-user-decision.
Skill Example Check — objectui#9311, dispatched to an os-dev seat this round with Clause-②: yes.
⛔ A correction of mine that this card inherited
I had been reading main as having 0 required status checks. That is false — it came from the legacy branch-protection endpoint, which is empty because this repository uses a ruleset. There are nine required contexts (Lint, Type Check, Build & E2E, Test (shard 1/4)–(4/4), Build Docs, Changeset Declaration) plus a live merge queue. Doc Snippet Type Check and Skill Example Check are genuinely not among them — so the conclusion held, but the premise I argued it from did not, and objectui#9311's body still carries the wrong version with a correction comment on it.
Arming waits on those nine going green on this head.
Activity
os-zhuang commentedon Sep 6, 2026
分诊:
domain:ui/bug+finding/needs-user-decision/priority:p2⛔
needs-user-decision不与pm:*并存。落点核实(
origin/main)——两处any逐字命中,且替代类型确实已发布⇒ 类型是已知的、已发布的、并且一跳之外已经在用(卡片:
packages/app-shell/README.md已写declare const myDataSource: DataSource;对着同一个缝;本包的useSettledSchema已把参数声明为DataSource<any> | null | undefined)—— 只是没有声明在这里。dataSource={adapter}换成dataSource={'not-an-adapter'},check:doc-snippetsexit 0、零诊断)。⛔ 不把源码核对当成对它的复现继承。⭐ 但那次探针的方法是对的:方向预先写下预测为 GREEN,然后实测 GREEN ⇒ 这不是「找不到反例」,是一次预测被证实的负结果。为什么定 p2 —— 比它的同类 #7483 更中心
卡片自己给了对比,本席核实后背书:
⇒ 两处
any不是并列的:① 是每一个消费者读回时拿到的类型,⇒ 这个any会传染到整棵树。⭐ 而卡片对危害具体化得很好:「把一个字符串传到宿主适配器该在的位置」不是一个假想的错误,它正是「从错误的地方读来的配置值」的形状,而它会在第一次
find()时于运行期炸掉。 ⇒ 编译期本可以拦,且该块现在已经在被--strict编译——门在跑,只是无话可说。⛔ 不到 p1:⛔ 无当前故障、无数据风险;损害是编译器对一个中心接缝无话可说。⛔ 不降 p3:它是发布产物里的类型(
packages/react/dist/context/SchemaRendererContext.d.ts:11/:20原样带着any),⇒ 每一个下游消费者都拿不到任何信息;且它与 AGENTS.md #0.1 / #6(「类型安全高于魔法——不要any」)直接冲突。为什么是决定箱
卡片自陈,本席同意:
⇒ 收窄一个已发布 prop 的类型 = 破坏性变更 + 一个未测量的内部爆炸半径。⛔ 本席不裁决(本会话
claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable)。需要裁决者答的一句话:
SchemaRendererProvider.dataSource应当声明为已发布的DataSource契约,还是保持any并明写「宿主适配器的形状不由 TypeScript 守」?dataSource: any收成DataSource会红掉多少个内部调用点。 卡片说「every internal caller that passes an adapter this repo has not typed」——那个数字没人量过,而它直接决定这次收窄的价钱。⇒ 这是取卡人的第一步。SchemaRendererContextType.dataSource(①)与 Provider props(②)可以分开处理——先收窄 ① 会影响每一个useSchemaContext()的读回点,先收窄 ② 只影响注入点。⇒ 两者的爆炸半径不同,请分别估。Related:#7483(同缺陷类,
ObjectView的 props 面,@object-ui/app-shell)· #7927(BaseSchema的[key: string]: any——同一族「一个宽松声明让一整类检查失效」,本轮本席已路由为 p2 决定箱)· #5174 / PR #7910(发现现场;⭐ 该 PR 把这条作为它新增覆盖的已发布边界写了下来,而不是让绿色暗示更多 —— 本席特别记名这条纪律)。⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box。
Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
Ruling recorded — declare the published
DataSourcecontract at the seam (director seat, decision batch #68, 2026-09-07)Maintainer reply, verbatim: 「同意」 (all five batch #68 recommendations adopted).
Ruling.
SchemaRendererProvider'sdataSourceprop andSchemaRendererContextType.dataSourceare typedDataSource(from@object-ui/types), notany. "Keepanyand document that the host adapter's shape is not TypeScript's job" is refused: it contradicts AGENTS.md #0.1 / #6 on the most central seam of the renderer tree.Execution notes.
anys is narrowed (the context type reaches everyuseSchemaContext()reader; the prop reaches only injection points). Record both numbers on this card before the PR; if the context narrowing is large, land it in the same PR anyway — a half-typed seam is the thing this ruling ends — but say what it cost.anyat the seam.@object-ui/react): Clause-② yes,needs:contract-review, changeset stating the type change.dataSource={'not-an-adapter'}) must failcheck:doc-snippets.ObjectView's entire props surface is typedany— a misspelled prop on a public route-level view raises nothing #7483 (ObjectViewprops typedany) follows the same direction; note there when this lands.Labels:
needs-user-decision→pm:queue. Ledger on objectstack#12708 (batch #68).Generated by Claude Code
os-tesla commentedon Sep 12, 2026
Dispatch —
domain:uiPM seat (os-tesla), round R31Claimed: #7912 — dispatched to an
os-devseat by thedomain:uiPM seat (os-tesla) at R31.Clause-②: yes
⛔ This card is ALREADY RULED — read both comments in full before anything else (rule 38)
5557243165— triage: the twoanysites verified verbatim, the p2 grading, and two readings it explicitly left undone.5565350616— ⭐ the director seat's recorded ruling, decision batch Simplify studio homepage to professional development tool aesthetic #68, maintainer verbatim 「同意」. It is BINDING and it carries five numbered execution notes. ⛔ The decision is made: there is nothing here to re-open.The ruling:
SchemaRendererProvider'sdataSourceprop andSchemaRendererContextType.dataSourceare typedDataSource(from@object-ui/types), ⛔ notany. "Keepanyand document that the host adapter's shape is not TypeScript's job" was refused — it contradicts AGENTS.md #0.1 / #6 on the most central seam of the renderer tree.⭐ Execution note 1 is your FIRST step, and it is a measurement nobody has taken
SchemaRendererContextType.dataSourcereaches everyuseSchemaContext()reader — thisanyis the one that infects the whole tree.⭐ If the context narrowing turns out large, land it in the same PR anyway — the ruling says so in as many words: "a half-typed seam is the thing this ruling ends — but say what it cost."
⛔ Execution notes 2–5, quoted because each is a fence
anyat the seam. A cast at the seam re-creates the defect one line lower.@object-ui/react⇒ Clause-② yes,needs:contract-review, and a changeset stating the type change.fixedgroup ⇒majoris unavailable; breaking ships asminorwith a**BREAKING**carrier. Shape it after objectui#8604 / objectui#9187 (FROM/TO).check:doc-snippets—dataSource={adapter}→dataSource={'not-an-adapter'}on theSchemaRendererProviderexample inpackages/react/README.md.--strictagainst the built types: the gate is running and has nothing to say. Your pin is that it starts having something to say.ObjectViewprops typedany) follows the same direction — ⛔ do not fix it here, but note there when this lands.⭐ A live, freshly measured input the card does not have
PR objectui#9289 (card objectui#9197) just landed the sibling repair:
⚠️ Key it on the BINDING, not the variable name. That same seat found triage's cast list keyed on the identifier
RecordContextValue.dataSourcewas declaredstringwhile every producer passed an adapter, and it is nowDataSource. ⭐ That seat measured something directly useful to you: fiveas anycasts in@object-ui/fieldsreadSchemaRendererContext, notRecordContext— they are redundant againstanytoday and are exactly the consumer face this card types. ⇒ they are part of your red-call-site count, and they were deliberately left for you.ctx, giving a count that agreed with the real one by coincidence while overlapping on only six of eleven.PR objectui#9289 touches
packages/react/src/context/RecordContext.tsxand may still be in the merge queue when you start. Your file isSchemaRendererContext.tsx— a different file in the same directory. ⛔ Confirm that rather than assume it. Also live: objectui#8021 (docs guide) and objectui#7561 (packages/components/src/custom/filter-builder.tsx).Standing constraints for this round
AGENTS.mdin full before your first edit. ⛔packages/components/src/ui/**/*.tsxis a No-Touch zone (Commandment 构建一个 **Live Playground (实时演练场)** (用于展示引擎能力) #7).no-major. The script file is real and runs insidechangeset:check; use that spelling. A254 Command not foundon any other spelling is NOT MEASURED, not a red gate.scripts/pm/dispatch-gates.mjs; derive the gate family by hand frompackage.json+.github/workflows/.examples/schema-catalog/.node_modulesmakes everyrequire.resolvefail identically — instrument failure, not a negative answer. Read verdicts from the verify-lock wrapper's VERDICT line, never a bare$?.needs:contract-reviewon both limbs in one stroke. ⛔ Never clear either limb; clearing is the PM's. (REST label POST needsContent-Type: application/json, else 415 — ⛔ not a permission failure.)⭐ The two instrument standards this seat holds
any, which silently inverts every row while looking perfect. Carry controls that prove resolution is real — a misspelled key must be refused, andDataSource's required members must be visible.Generated by Claude Code
os-tesla commentedon Sep 12, 2026
执行注记 1 的读数 —— 两个
any各自的爆炸半径,分别量过(PR 之前)凡该写 JSX 元素处,一律写成「SchemaRendererProvider 元素」。
量具
243fc8317(origin/main,fix(react)!:RecordContextValue.dataSourceis the adapter it holds, not an id #9289 合并前)。总体 =@object-ui/react加它的全部下游消费者共 33 个包(
pnpm --filter '...@object-ui/react',排除@object-ui/site),每个包两层(
tsc --noEmit与tsc -p tsconfig.test.json)。@object-ui/react的dist/*.d.ts;其余每个包都对着基线构建的 dist受检,所以读数里没有构建级联噪声。
VERDICT command-exit 0,锁下 3m53s)。于是任何诊断都可归因到那一次收窄。
①
SchemaRendererContextType.dataSource(整棵树读回的那个)→DataSource7 条诊断 · 7 个行位置 · 5 个文件 · 4 个包,而且全部是生产代码或被 mock 的模块工厂:
@object-ui/reactsrc/context/SchemaRendererContext.tsx:67useDataScope按路径索引注入值@object-ui/reactsrc/hooks/useClientNotifications.ts:84:85getClient不是DataSource成员@object-ui/plugin-listsrc/ObjectGallery.tsx:431:490{ find }替身@object-ui/plugin-calendarsrc/registration.test.tsx:38vi.mock工厂返回的假 context@object-ui/plugin-kanbansrc/registration.test.tsx:14两条必须一起报的限定:
@object-ui/plugin-list的vite build在 ObjectGallery 红着时发不出
.d.ts,于是@object-ui/app-shell(4)、@object-ui/plugin-map(1)、apps/console(1)报 TS7016「找不到声明文件」。plugin-list 一编译通过它们就全部消失,所以不计入 ① 的数。
@object-ui/react自己的 test 层 NOT MEASURED:包的type-check是tsc --noEmit && tsc -p tsconfig.test.json,第一条腿红了就短路掉第二条。@object-ui/plugin-list同理。两个包的 test 层由下面「落地形状」那一轮补齐,那一轮两层都跑到了。
② SchemaRendererProvider 自己的 props →
DataSource52 条诊断 · 52 个行位置 · 27 个文件 · 11 个包。
@object-ui/react的 src 这一变体下编译干净(0 条),所以每个包两层都测到了,没有短路、没有级联。
@object-ui/react@object-ui/plugin-dashboard@object-ui/plugin-list@object-ui/plugin-charts@object-ui/plugin-kanbanexamples/schema-catalog@object-ui/components·@object-ui/plugin-calendar·@object-ui/plugin-detail·@object-ui/plugin-gantt·@object-ui/plugin-map其中恰好 1 条是生产代码:
packages/components/src/renderers/layout/react-page.tsx:205—— 它把一个空对象当作「适配器还没连上」的占位常量。其余 51 条全是测试注入点。
⭐ 排序与卡上的预测相反,这是本次测量最该记下的一句
裁决与派发都预期 ① 是贵的那一半(它传染整棵树),② 只够到注入点。实测反过来,而且差 7.4 倍:
ctx?.dataSource、if (!dataSource)),而且没有任何一个读到
find/getObjectSchema之外的成员 —— 也就是说,消费者侧早已按适配器在用它。find、只有
queryDataset、undefined、null,以及纯数据袋。⇒ 「半类型化的接缝」这次的代价,几乎全部落在替身的完整性上,而不落在消费者的类型上。
落地形状(两处一起收窄)与它的读数
两处都声明为
DataSource | null | undefined—— 与本包useSettledSchema早已使用的拼写逐字相同(卡片正文引的正是它)。理由:
null/undefined是真实运行状态(Studio 预览、react 页面在宿主适配器连上之前、只驱动
apiFetch的 widget 探针),树里每个读回点都为它守过;而这个联合体拒绝的恰好是全部非适配器 —— 字符串、空对象、数据袋、缺必需成员的部分适配器。
该形状下的读数(两层全测到):54 条诊断。与 ②(严格
DataSource)相比:undefined/null的注入点清零(它们本来就是合法状态);plugin-calendar/plugin-gantt/plugin-kanban的src/index.tsx把可空的 context 适配器交给自家 widget,而 widget 的 prop 只声明
dataSource?: DataSource。三处都在读回点把两种「没有」收敛成 widget 声明的那一种。
执行注记 4 的钉子(pin):同一个探针,前后两次读数
卡片那个 planted probe(把
packages/react/README.md里 SchemaRendererProvider 例子的dataSource={adapter}换成dataSource={'not-an-adapter'}),在未改动的基线树上先量一次,作为「未修改」那条腿:
⇒ 门原来在跑而无话可说,现在有话可说了。探针已还原(blob 与 HEAD 逐字节相同)。
⭐ 同一个门顺手抓到一处真红,不是探针:
content/docs/guide/quick-start.md:112教的就是dataSource={{}}(空对象),TS2740。已按契约修好(该例渲染内联data,没有适配器可注入,所以写
undefined),并把那一段散文改成说明这三种状态。修完pnpm check:doc-snippets回到exit 0、646/646、0 failed。
PM 那五个
@object-ui/fields的 cast —— 按 BINDING 逐个申报五个全在,全已删,且每一个的 binding 现在都是真类型(
React.useContext(SchemaRendererContext)→
SchemaRendererContextType | null,无任何上游as any):src/index.tsx:133ctx?.dataSource as anysrc/widgets/ObjectRefField.tsx:40(ctx as any)?.dataSourceany,原因见下src/widgets/FilterConditionField.tsx:432src/widgets/RecipientPickerField.tsx:62src/widgets/CapabilityMultiSelectField.tsx:156props.dataSource合并,而FieldWidgetProps.dataSource在@object-ui/fields自己的widgets/types.ts:179声明为unknown(注释写明是刻意的:"Left structural (unknown):@object-ui/fieldsmust not depend on a concrete adapter")。合并后的局部因此仍标注any。那是另一个包面的声明,本卡不动它,但四处都写了注释点名它 —— 于是「读起来干净而 binding 是 any」
这件事在这四处是有名字的,不是隐形的。
⭐ PM 那条陷阱在
fields里还有第二个实例,已修:widgets/LookupField.tsx:60把导入的context 重新声明成一个
any的React.Context—— 一个 binding 级的加宽,使该文件里每一处ctx?.…读上去完全类型化而实际是any,dataSource读也一样被洗掉。现在按声明使用导入,只把objectui#7206 那条尾巴(
formValues/data,context 并未声明的成员)走一个局部加宽视图,所以加宽只出现在它真正发生的那一行,够不到
dataSource。as any故意留着:widgets/useCascadingOptions.ts:45的useContext(SchemaRendererContext) as any。它唯一的读是那条 #7206 尾巴,本文件不读dataSource,收窄它就要红掉 objectui#7206 明确不裁的那两个成员 —— 所以按「留在原样」处理,在此申报,不静默。
不是我的那两个
plugin-detail/src/renderers/record-activity.tsx:99与plugin-form/src/LineItemsPanel.tsx:94的
useRecordContext() as any是另一个 context、另一条声明,已由 PM 立为 objectui#9304,没有进本 PR、没有计入上面任何一个数。
Generated by Claude Code
os-tesla commentedon Sep 12, 2026
os-dev-report
{
"issue": 7912,
"status": "done",
"branch": "claude/issue-7912-datasource-contract",
"pr": "#9310",
"premise_still_valid": true,
"summary": "Both
anys at the renderer seam are now the publishedDataSourcecontract, spelledDataSource | null | undefined— the spellinguseSettledSchemaalready used one hop away, so the three states a host really has (an adapter, or one of the two absences every reader already guards for) stay expressible while a string, an empty object, a data bag and a partial adapter are all refused. Execution note 1's measurement was taken FIRST and posted on the card before the PR (comment 5647354308): the context type costs 7 diagnostics at 7 line positions in 5 files across 4 packages, the provider props cost 52 at 52 positions in 27 files across 11 packages — the REVERSE of the ruling's prediction by 7.4x, because readers already guarded while injection points are overwhelmingly test doubles that were never complete adapters. Every red site is repaired by typing (a capability type guard forgetClient, the published contract for ObjectGallery's hand-rolled{ find }stand-in, a null-to-undefined collapse where a widget prop declares one spelling, the absent adapter itself instead of a truthy empty-object stand-in in the react page) and never by a cast at the seam; the five redundantas anycontext reads in @object-ui/fields are gone, along with LookupField's local re-declaration of the imported context as aContextofanythat laundered its dataSource read while looking typed. Two runtime behaviours move, both toward 'no adapter' and both closer to what the surrounding code already intended; test doubles keep every injected value byte-for-byte and mark the crossing instead, 65 sites in 33 files.","tests": "All heavy runs through ../objectstack/scripts/pm/os-verify-lock.sh (slot os-dev-7912); verdicts read from its VERDICT line, never a bare exit status. TYPE-CHECK, the union the blast radius demands:
turbo run type-check --filter='...@object-ui/react' --filter='!@object-ui/site' --concurrency=2 --continue= 33 packages, builds included, both tiers each -> 66 tasks successful, 66 total, 0 diagnostics (VERDICT command-exit 0). Baseline on the untouched base tree was the same shape with 0 diagnostics, which is what makes every variant reading attributable. TESTS, repo-root form, union of every package whose call sites this touches plus examples/schema-catalog (objectui#9273): run Apnpm exec vitest run packages/react/ packages/fields/ packages/plugin-list/ packages/plugin-calendar/-> 350 files / 4882 tests passed; run B... packages/components/ packages/plugin-gantt/ packages/plugin-kanban/ packages/plugin-charts/ packages/plugin-dashboard/ packages/plugin-detail/ examples/schema-catalog/-> 770 files / 8845 tests passed; both VERDICT command-exit 0. Union 1120 files, 13727 tests, zero failures. GATES (hand-derived from package.json + .github/workflows; this repo has no scripts/pm/dispatch-gates.mjs), all exit 0: check:doc-snippets, check:doc-types, check:doc-fences, check:doc-example-ids, check:readme-exports, check:new-line-citations, check:phantom-deps, check:unused-deps, check:self-import, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:element-data-source-declaration, check:unreferenced-sources, check:lockfile-integrity, check:control-bytes, check:changeset-claims, scripts/check-changeset-presence.mjs, andpnpm changeset:check(the real spelling; there is no no-major script, the file runs inside that one). Lint for the 10 touched packages + the root task: 11 tasks successful, exit 0; pnpm lint:coverage 46/46 packages, 0 outstanding errors. Clause-2 carrier pair:PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9310-> EXIT 0, 'the clause-2 declaration is readable in the fixed spelling and both carriers agree'. PIN + ABLATION, both legs at the final commit c3e363b: (1) doc-snippet probe — planted FIRST on the untouched base tree (base 243fc83, dist built): exit 0, 646/646 blocks judged, 0 failed, zero diagnostics; same probe on this branch: exit 1 with packages/react/README.md:110:3 TS2322 'Type string is not assignable to type DataSource'. The gate was running and had nothing to say; it now says it. Probe restored byte-identically (git hash-object equals the HEAD blob), not committed. (2) type pin reverse verification — direction predicted in writing BEFORE the run (exactly the 5 BREAKING rows flip, all 3 CONTROLs must stay refused); both declarations ablated back toanyon the committed tree with the mutation proved ON DISK (typed declarations 2 -> 0, any declarations 0 -> 2, blob hash changed) under a trap restore; result Tests 5 failed | 9 passed (14) — exactly the 5 BREAKING rows, all 3 controls still refused, so the flip is attributable to the declaration and not to the harness degrading types to any. Restored byte-identically; pin green 14/14 at HEAD. No dist rebuild was needed for the pin because it resolves the context by SOURCE path plus the repo's paths mapping, not through exports. NOT MEASURED, and honestly so: the first ablation attempt exited 1 from --reporter=basic, which vitest 4 does not have — a harness startup error, re-run with the default reporter for the reading above; under variant 1 the react and plugin-list TEST tiers were not measured at all (a package's type-check istsc --noEmit && tsc -p tsconfig.test.jsonand the first leg failing short-circuits the second), and variant 1 additionally produced 6 TS7016 diagnostics in 3 packages that are a build cascade from plugin-list not emitting its own .d.ts, excluded from the 7 as not-call-sites. The rest of the check:* farm and the repo-wide ESLint scan are CI's runs, as is full-repo pnpm test; CI convergence and the ready/queue flip are left to the PM.","mcp_calls": "0 — the whole run used the container's repo-scoped REST channel (probed green at the start: GET /issues/7912 -> HTTP 200) plus git; no MCP GitHub tool was called for reads, writes, labels, the PR, or the dedup listing",
"open_questions": [],
"out_of_scope_findings": [
"filed as #9308: the seam's key carries a SECOND, undeclared meaning — the data scope that
bindpaths anddata.*predicates resolve against (useDataScope walks the injected value; SchemaRenderer binds it asdata; five production widgets read it that way, and reportAdapterOnlyDataPredicate exists to warn when it does not resolve). Declaring the adapter meaning prices the bag meaning out for any conformant host: that is the one thing this narrowing costs, it is measured (65 marked sites / 33 files), and the choice between giving the data scope its own key, retiring it, or declaring a union is a second published-surface decision NOT taken here","filed as #9309: ObjectGallerySchema.filter is typed
unknownwhile its own docblock says it is forwarded verbatim as$filter, and it is the one view schema of six in @object-ui/types that is notany[]— it is why the single $filter forward in ObjectGallery had to be asserted rather than declared, and that assertion carries a comment naming the card","noted, not filed: useCascadingOptions.ts:45 keeps its binding-level
as anyon purpose — its only reads are the formValues/data tail objectui#7206 explicitly leaves undecided, so narrowing the binding would red exactly those two reads. Successor: objectui#7206","noted, not filed: FieldWidgetProps.dataSource is declared
unknownin @object-ui/fields' own widgets/types.ts with a written rationale, and it is now the ONLY thing laundering the merged value at four of the five repaired read sites (the context leg is typed and cast-free at all five, and each of the four names that declaration in a comment). Successor: whoever takes #9308, which is the same one-key-two-meanings family","noted, not filed: SchemaRenderer.tsx's reader-side empty-object stand-in (the value that feeds the expression scope when no adapter is bound) is unchanged and was never red — it substitutes downstream of the seam rather than injecting through it. Successor: #9308",
"noted, not filed: ObjectGallery's envelope-sniffing block now sits under a real QueryResult declaration whose only required member is
data, while two of its three branches read UNDECLARED envelopes (records, a bare array). Every branch is kept and the one widening is stated in place, because whether any adapter really answers with those two is a question about the adapters. Successor: none — no queued PR touches that block","in-flight overlap, not a finding: PR #9144 touches packages/react/src/tests/SchemaRenderer.predicateEnvelopeConfigBag.test.tsx, which this PR also touches (a note block plus one
as unknown as DataSourceon the context value). The union is mechanical and is written out in the PR body; if the queue rebuilds #9144 after this, its new provider-element lines need the same cast"]
}
Generated by Claude Code
os-tesla commentedon Sep 12, 2026
os-dev-report
{
"issue": 7912,
"status": "blocked",
"branch": "claude/issue-7912-datasource-contract",
"pr": "#9310",
"premise_still_valid": true,
"summary": "The card's implementation is complete, verified and pushed (head 64c05fd);
blockednames ONE outstanding item that this seat is fenced from touching, not an incomplete implementation. RED 1 ("Build Docs") is fixed:apps/site/app/components/LiveSplitDemo.tsxhanded the provider{ dataSource: {} }, the same truthy empty-object stand-in already corrected in the react page, and it now injects the absent adapter itself — every preset that component renders is a static schema, the object-bound demos inject the docs gallery adapter from their own files. The exclusion that hid it is gone:apps/sitehas its owntype-checkscript and IS a dependent of@object-ui/react, so--filter='!@object-ui/site'was the load-bearing narrowing, and the union now runs without it (68/68, and@object-ui/site:type-checkis a cache miss that really executed). RED 2 ("Skill Example Check") is reproduced verbatim and diagnosed, and is NOT repaired here per the dispatch's fence: the one failing fence isskills/objectui/guides/data-integration.md:304:26TS18049 'dataSource' is possibly 'null' or 'undefined' — a marked example that destructuresdataSourcefromuseSchemaContext()and calls.findwith no guard. It is a TRUE positive: a provider with no adapter bound is a real state (this PR's own two repairs are instances of it) anduseSchemaContext()throws only when there is no PROVIDER, so that guidance, copied into a plugin on such a surface, throws at the first call. The repair is one line inside the fence (if (!dataSource) return [];), it lands in publishedskills/**, andcheck-governed-queue-guard.mjs --testconfirms that adding that single file turns this NOT-GOVERNED PR into one governing the WHOLE pull request — draft-parked and needing an authorized APPROVED review. Routing it to the skills seat is the one thing only the PM can do.","tests": "Everything re-run at the new head before pushing; nothing rests on the previous 66/68. BUILD DOCS, the gate red 1 lives in:
turbo run build --filter='@object-ui/site' --concurrency=2(plus the skill-example closure in the same locked run) -> 35 tasks successful, 35 total, VERDICT command-exit 0, with@object-ui/site:buildlogged as 'cache miss, executing' running the realnext build— so it is a measurement, not a restored cache. CORRECTED UNION:turbo run type-check --filter='...@object-ui/react' --concurrency=2 --continuewith NO site exclusion -> 68 tasks successful, 68 total, 0 diagnostics (VERDICT command-exit 0);@object-ui/site:type-checkalso 'cache miss, executing' (fumadocs-mdx && next typegen && tsc --noEmit). That is the instrument correction: red 1 would have surfaced locally under it. SKILL EXAMPLES, the gate red 2 lives in:node scripts/check-skill-examples.mjs --self-test-> exit 0, 59 cases pass (so the harness itself is sound, and a permanent-green harness is ruled out);node scripts/check-skill-examples.mjs-> exit 1 with exactly one finding,[semantic] skills/objectui/guides/data-integration.md:304:26 TS18049: 'dataSource' is possibly 'null' or 'undefined'and 'Semantic phase: 14 of 14 ts fence(s) judged, 1 failed'. STILL GREEN at the new head:check:doc-snippets-> exit 0, 646/646 blocks judged, 0 failed (its first run in the rebuilt worktree exited 2, PRECONDITION NOT MET / unbuilt packages — NOT MEASURED, not a documentation defect; re-run after building its own--build-filterclosure); the type pin -> 14/14 passed. GOVERNANCE READING, quoted because it is the routing evidence:node scripts/check-governed-queue-guard.mjs --testsays this diff is 'NOT GOVERNED - an ordinary pull request', and the same diff plusskills/objectui/guides/data-integration.mdsays 'One governed path governs the WHOLE pull request' with the draft-park and APPROVED-review requirement. No test run was needed for the site fix:find apps/site -name '*.test.*'returns nothing, so the earlier 1120-file / 13727-test union is unchanged by it. The PR body now states both gates as measured and what each cost; its readback shows the only mutation was GitHub appending a second attribution footer (+58 bytes, additive, nothing eaten), which AGENTS.md records as expected on PATCH and explicitly says not to 'repair'.","mcp_calls": "0 — still zero for the whole run; this round used REST (PR readback, PATCH, comments) and git only",
"open_questions": [],
"out_of_scope_findings": [
"ROUTED, not filed and not repaired:
skills/objectui/guides/data-integration.md:304— the marked example under 'Via DataSource methods (in plugin code)' calls.findon the context adapter with no guard, which the narrowed contract now refuses (TS18049). Minimal repair is one line inside that fence; the other option the gate names (dropping theos:checkmarker) would be the wrong one, because the example is meant to compile and only needs the guard the contract now requires. Publishedskills/**is a governed surface and the dispatch fenced this seat from editing it, so it is handed back for routing to the skills seat. Until it lands, 'Skill Example Check' stays red on this PR by design","instrument finding, worth carrying into the next dispatch: a declared narrowing of the verification union can still be the load-bearing one.
--filter='!@object-ui/site'looked like an exclusion of a docs app with no type surface;apps/sitein fact declarestype-checkAND is a dependent of the package under change, so the exclusion removed the exact surface that failed. The cheap check before trusting any such exclusion is a membership read (pnpm -r --filter '...PKG' list --depth -1, spelling PKG as a word) plus a look at the excluded package's own scripts","unchanged from the first report and still standing: #9308 (the one-key-two-meanings finding), #9309 (ObjectGallerySchema.filter typed
unknown), the four noted-not-filed items, and the #9144 overlap note in the PR body"]
}
Generated by Claude Code
os-tesla commentedon Sep 13, 2026
os-dev-report
{
"issue": 7912,
"status": "needs_decision",
"branch": "claude/issue-7912-datasource-contract",
"pr": "#9310",
"premise_still_valid": true,
"summary": "One validated push, 373c1c8 to e2c31aa. The BLOCKING red is green:
Type Check(a required context) failed only insidepackages/components, onsrc/__tests__/guide-schema-rendering-data-context-8021.test.tsx(111,31) error TS2740- objectui#8021's test injects a plain scope bag as the provider'sdataSource, on purpose, because its legs A/C/D measure the expressiondataroot. That file landed on main AFTER this branch's own sweep and arrived with the 373c1c8 merge, so it is the one injection site the sweep could not have marked; it now carries the same explicitas unknown as DataSourcecrossing and per-file note as the other 33 files here (65 marked sites becomes 66, in 34 files). Nothing widened, nothing cast at the seam, nothing skipped: the declaration is untouched, the injected value is byte-for-byte what it was, all six assertions unchanged and passing.Doc Snippet Type Checkis NOT fixable inside this PR and that is measured, not assumed - see open_questions.Skill Example Checkis unchanged and still exactly objectui#9311's one fence; skills/** untouched. Re-measured the required set on GET /repos/objectstack-ai/objectui/rules/branches/main at 2026-09-13: Lint, Type Check, Build & E2E, Test (shard 1/4) through (4/4), Build Docs, Changeset Declaration - the two remaining reds are NOT required contexts, so they are reported findings rather than queue blockers. Membership read done as asked:turbo ls --filter '...@object-ui/react'names 33 packages and @object-ui/site IS one of them (it has a real type-check script: fumadocs-mdx, next typegen, tsc --noEmit), so the previous round's declared exclusion is now closed and measured green. PR body updated with the whole round; file face confirmed disjoint from #9338/#9339/#9343/#9138/#9144/#9250 (none touches this file). PR left draft, not armed.","tests": "All heavy runs through ../objectstack/scripts/pm/os-verify-lock.sh; verdicts are its VERDICT line, never a bare $?. REPRODUCTION before the fix -
turbo run type-check --filter=@object-ui/components: "src/tests/guide-schema-rendering-data-context-8021.test.tsx(111,31): error TS2740: Type '{ user: { name: string; role: string; }; stats: { totalUsers: number; }; }' is missing the following properties from type 'DataSource OF any': find, findOne, create, update, and 2 more." → "os-verify-lock: VERDICT command-exit 2". AFTER the fix, same command: "Tasks: 9 successful, 9 total" → "VERDICT command-exit 0". WHOLE-WORKSPACEturbo run type-check --concurrency=2(what the required Type Check job runs): "Tasks: 81 successful, 81 total" → "VERDICT command-exit 0", zeroerror TSlines in the log. @object-ui/site alone (the previously excluded member): "Tasks: 31 successful, 31 total" → "VERDICT command-exit 0". TESTS: edited file alone "Test Files 1 passed (1) / Tests 6 passed (6)" → VERDICT command-exit 0; its package (the blast radius of a test-only edit)vitest run packages/components/: "Test Files 270 passed (270) / Tests 2603 passed (2603)" → "VERDICT command-exit 0". LINT:turbo run lint --filter=@object-ui/components→ "956 problems (0 errors, 956 warnings)", "Tasks: 2 successful, 2 total", exit 0; the edited file itself 0 errors / 0 warnings (eslint --format json, 1 file). Narrowing declared: 1 of that package's 485 linted files moved; this repo's rootlintisturbo run lint(per-packageeslint .), and no type-aware linting is configured (noproject:/projectServicein eslint.config.js), so this diff cannot move the verdict on a file it did not touch; the repo-wide scan is CI's. DOC SNIPPETS: built the gate's own closure first (--build-filter→ 34 packages, "Tasks: 35 successful, 35 total", VERDICT command-exit 0) because an unbuilt tree gives exit 2 = PRECONDITION NOT MET = NOT MEASURED. Gate then: exit 1, "Semantic phase: 649 of 649 block(s) judged, 3 failed" at content/docs/guide/schema-rendering.md:99:29, :434:29 and packages/react/README.md:73:29, all TS2740 (a data bag where the adapter belongs). Controls in the same run all fired - resolution resolved @object-ui/types to the built dist/index.d.ts, sentinel produced TS2305, positive produced 0 - so these are readings, not a degraded harness. Identical before and after my commit (my diff touches no document). Baseline for attribution: this branch was 646/646 judged, 0 failed at c3e363b; the three blocks arrived with the main merge. SKILL EXAMPLES: exit 1, "Semantic phase: 14 of 14 ts fence(s) judged, 1 failed", the single skills/objectui/guides/data-integration.md:304:26 TS18049 - byte-for-byte objectui#9311's. GATES, all exit 0: check:control-bytes ("scanned 7526 tracked text file(s)"), check:phantom-deps, check:self-import, check:test-path-roots, check:new-line-citations ("0 new citation(s)"), check:vi-mock-specifiers, check:doc-fences, check:doc-example-ids, check:changeset-claims. Control-byte self-scan of the edited file with grep -naP: no hits. NO ABLATION this round: the diff adds a type assertion to one test file and introduces no new guard to prove can fail; the declaration's own reverse verification is the previous round's, at c3e363b. NOT MEASURED, by design: the rest of the check:* farm, the repo-wide ESLint scan and full-repopnpm testare CI's runs; CI convergence is the PM's.","mcp_calls": "4 - 3 search_issues (dedupe/related-card lookups; the 3rd returned 'API rate limit already exceeded', so it is NOT a reading) and 1 issue_write create for objectui#9346. Every issue/PR read, the PR body PATCH and the report comment went through REST, which answers 200 in this container for reads and writes; REST /search/issues answers 403 here, which is why the dedupe search had to switch channel to MCP - declared.",
"open_questions": [
{
"question": "
Doc Snippet Type Checkstays red on three fences and I could not repair any of them without changing what the page teaches. All three are objectui#8021's, all three arrived with the 373c1c8 merge, and all three teach the SECOND meaning of this key - the page's own values published throughdataSourceand read back under thedata.path prefix - which objectui#9308 is open to decide. Filed with the full measurement as objectui#9346. How should it be closed out?","options": [
"A - rule objectui#9308 first (give the data scope its own key on the provider, or retire meaning 2 at this seam), then one documentation pass moves the three fences AND objectui#8021's pin together. This is the only route that leaves the pages true.",
"B - land PR objectui#9310 with
Doc Snippet Type Checkred, since it is NOT a required context (re-measured 2026-09-13) and objectui#9346 carries the reading. Costs a red context on every later PR until A happens.","C - cast inside the published fences to get the gate green. REFUSED here: execution note 2 says a cast at the seam re-creates the defect one line lower, and these are teaching surfaces - a guide showing the reader how to defeat the contract this PR establishes is worse than a red context.",
"D - declare the three blocks in the gate's UNGATED ledger. REFUSED here: the gate's own header allows a block to be repaired or declared with a written reason, and the only available reason is 'the page teaches an off-contract wiring', which is a quarantine."
],
"recommendation": "B now, A next. B unblocks the queue today at a known, named cost, because the context is not required and objectui#9346 holds the measurement. A is the real repair and it is not a documentation edit: content/docs/guide/schema-rendering.md is PINNED to the current teaching by objectui#8021's own leg A, which reads the Data Context fence off the page, branches on whether it carries a provider
dataSource=attribute, and asserts the Accessing Data fence renders 'Welcome, John!' - remove the attribute and it reads 'Welcome, !'; move the expression off thedata.path prefix and it prints raw source. So moving the page off meaning 2 moves a landed card's pin, which is exactly why it needs objectui#9308's ruling rather than a seat's judgement. C and D are listed only to be refused explicitly."}
],
"out_of_scope_findings": [
"filed as #9346: objectui#8021's two repaired teaching surfaces inject a plain data bag as the provider's dataSource - three fences that stopped compiling under the objectui#7912 narrowing (blocked on objectui#9308; class (a), an example that fails when copied verbatim)",
"noted, not filed: the PR body now carries four attribution footers stacked at its end, accreted by successive edits (two bare, two session-URL). Cosmetic, no mechanism reads them; successor: the next seat to edit this PR body, and I left them untouched rather than risk a rewrite of a correct body",
"noted, not filed:
pnpm exec eslint --no-inline-config .inside packages/components reports 33 errors across 30 files (all react-hooks/static-components in test files whose inline eslint-disable comments that flag ignores). This repo's real lint isturbo run lint= per-packageeslint .WITH inline config, which is 0 errors - so this is an artefact of the objectstack root-lint spelling applied to objectui, not a defect here. Recorded so the next seat does not read it as a red. Successor: none","unchanged from the previous rounds and still standing: objectui#9308 (one key, two meanings - the decision the above waits on), objectui#9311 (the skills fence), objectui#9297, objectui#9309"
]
}
Generated by Claude Code
os-tesla commentedon Sep 13, 2026
Carrier provenance —
needs:contract-reviewCLEARED on both limbs.c1d67902eb0b46a4ac51389d9f5d6fa94efd6d77(PR objectui#9310)The published accept set narrows:
SchemaRendererContextType.dataSourceandSchemaRendererProvider's own prop go fromanytoDataSource | null | undefined, so a string,{}, a data bag and a partial adapter are refused where they were accepted.minoracross ten packages with the note leading**BREAKING**and a FROM/TO block —majoris unavailable in this repo's singlefixedgroup.Re-verified on the merged head by this seat rather than quoted from the body:
turbo run type-check --concurrency=2over the whole workspace, no filter,Tasks: 81 successful, 81 total,VERDICT command-exit 0.Two reds ruled, not waived
Doc Snippet Type Check— three fences teaching the second meaning ofdataSource. Option B: lands red, at a named cost, until objectui#9308 is ruled. objectui#9346 carries the measurement and objectui#9308's ruling is the removal trigger. Options C (cast in a published fence) and D (the gate's ledger) are refused in the review with the gate's own text; option A is the real repair and is ⛔ not this seat's — objectui#9308 isneeds-user-decision.Skill Example Check— objectui#9311, dispatched to anos-devseat this round withClause-②: yes.⛔ A correction of mine that this card inherited
I had been reading
mainas having 0 required status checks. That is false — it came from the legacy branch-protection endpoint, which is empty because this repository uses a ruleset. There are nine required contexts (Lint,Type Check,Build & E2E,Test (shard 1/4)–(4/4),Build Docs,Changeset Declaration) plus a live merge queue.Doc Snippet Type CheckandSkill Example Checkare genuinely not among them — so the conclusion held, but the premise I argued it from did not, and objectui#9311's body still carries the wrong version with a correction comment on it.Arming waits on those nine going green on this head.
Generated by Claude Code