Skip to content

bug(skills): testing.md Pattern 5 gates on a bare userRole root nothing publishes — copy the example and the assertion throws #9380

Description

@os-tesla

Found by the os-dev seat implementing objectui#9370 (PR #9378) while enumerating every ${data.…} and every expression example under skills/. This one is independent of objectui#9308: it is wrong on main today, and it stays wrong after that ruling lands.

The example

skills/objectui/guides/testing.md, "Pattern 5: Component rendering with SchemaRenderer", measured on origin/main 69aa9c017, lines 130-169. The fence builds a provider bag and then asserts the gated node is on screen:

const dataSource = { users: [...], userRole: 'admin' };
...
schema={{ type: 'text', content: 'Secret', hidden: '${userRole !== "admin"}' }}
...
// admin should see it
expect(screen.getByText('Secret')).toBeDefined();

userRole is spelled as a bare root. Nothing publishes it as one. SchemaRendererProvider's dataSource never spread its keys as bare names — the guide's own sibling skills/objectui/guides/auth-permissions.md documents exactly that trap at lines 319-327 ("Keys of the dataSource object are reachable only under the data. root — they are not also spread as bare names").

Measured, not inferred

On the built evaluator (packages/core/dist, tree 69aa9c017), evaluateCondition('${userRole !== "admin"}', scope):

scope hidden verdict node on screen
what the OLD renderer built: { data: { userRole: 'admin' } } true NO
what the renderer builds after objectui#9308: nothing published true NO
what the example ASSUMES: { userRole: 'admin' } false YES

So the node is hidden, getByText('Secret') throws, and the published test pattern fails for a reader who copies it verbatim. It is the fail-soft leg: userRole is not in scope, the expression is unevaluable, and the condition path returns true.

Why no gate sees it

--measure on scripts/check-skill-examples.mjs puts this fence in the root-bound refusal set — it imports vitest and @testing-library/react, which only the repository ROOT manifest declares, so the bound keeps it out of the semantic program and it is never type-checked. It is also unmarked, so it is outside the opt-in population either way. And type-checking would not catch this regardless: the defect is a runtime scope fact, not a type.

This is the class the gate's own header names — a fence can be wrong in a way no gate sees — so it needs a human edit, not a marker.

Suggested shape

Either publish userRole as a real root through PredicateScopeProvider (the channel objectui#9308 makes canonical, and the one a reader should be learning in a testing guide), or spell the gate against a root the example actually publishes. ⛔ Do not "fix" it by asserting the node is absent — the comment // admin should see it says what the pattern is meant to demonstrate, and inverting the assertion would publish a test pattern that teaches the wrong wiring while passing.

⚠️ Governed surface (skills/**, GOVERNED_SURFACES skills-catalog): an agent drafts, a human merges. ⛔ No ready-flip, no queue, no auto-merge.

Dedup

Run over the 83 cards this repository's issue-list endpoint returns for labels=domain:skills, non-open cards included, with objectui#9370 as the known-hit control — the control was found, so the zero is a reading. The two prior testing.md cards are different subjects: objectui#7557 retired the KNOWN_ROOT_DEVDEP_EXAMPLES rows and objectui#7494 the KNOWN_BARE_ANY_EXAMPLES rows. Neither touches what this fence asserts.

⛔ Filed without labels — routing and priority are the PM seat's.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_01EBx9rvB7dufCz4at53x35U
    Account: huangyiirene (the seat's linked user as get_me answers it; the card's assignee)
    Branch: claude/issue-9380-testing-pattern5-data-root
    Worktree: objectui-issue-9380
    Domain: domain:devx
    Seat: domain:devx#2
    File surface: skills/objectui/guides/testing.md («Pattern 5» fence and its prose only); net skills line budget +6 (stop on breach; explain in the report)
    Container & model: S, not mechanical (published skill text), mode:subagent, model: fable (MANDATORY — dispatch-gates --tier --repo objectstack-ai/objectui skills/objectui/guides/testing.md printed Model tier — MANDATORY: claude-fable-5-1; no one-line exemption on skills/**)
    Clause-②: no — a published guide's example is corrected to the scope the renderer already publishes; no accept set or public contract surface moves
    Thread-read: none
    Serial constraints cleared: GET /pulls/{n}/files over all 22 open objectui PRs (2,016 filenames examined, read before this claim) — none touches skills/objectui/guides/testing.md. Governed surface (skills/** ⇒ Tier H): draft only, both authorised approvers requested, ⛔ never flipped ready / enqueued / approved / merged by this seat; the skills hunk is reviewed at the contract-review tier (an isolated at-tier review subagent, since this seat is not at tier).

    Premise re-checked at claim time on objectui origin/main 733fd5ac65: skills/objectui/guides/testing.md still spells the bare root — userRole: 'admin', in the data source and hidden: '${userRole !== "admin"}' in the schema.


    Generated by Claude Code

  2. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 9380,
      "status": "done",
      "branch": "claude/issue-9380-testing-pattern5-data-root",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10941",
      "pr_number": 10941,
      "pr_state": "draft, base main, head d1fda66e1a89350fb837204d3af833e328921006, opened as objectstack-fleet[bot] via the fleet-write relay (run 36400463636); assignee huangyiirene",
      "pushed_sha": "d1fda66e1a89350fb837204d3af833e328921006",
      "session": "session_01EBx9rvB7dufCz4at53x35U",
      "premise_still_valid": true,
      "summary": "The Pattern 5 fence of skills/objectui/guides/testing.md gated `hidden: '${userRole !== \"admin\"}'` on a root nothing published: the bag went into SchemaRendererProvider's dataSource, which is the CRUD adapter and (since objectui#9308, 852437297b) no expression root at all, so the predicate faulted (`userRole is not defined`), evaluateCondition failed soft to true, the node was hidden and getByText('Secret') threw. The fence now publishes the same keys through PredicateScopeProvider (the channel every sibling guide teaches), the admin assertion is kept, a viewer leg is added via rerender so the test named 'hides component when hidden expression is true' exercises its hidden branch once, and two prose lines above the fence name the channel and the fail-soft trap. Reproduced red on the current renderer, green after; scratch tests deleted; no permanent test because no existing test pins this guide's examples.",
      "hypotheses": {
        "H1": "Re-measured on origin/main 733fd5ac65: testing.md Pattern 5 still built `userRole: 'admin'` in a dataSource bag and gated `hidden: '${userRole !== \"admin\"}'` (bare root) — confirmed. The card's second claim is STALE: auth-permissions.md no longer says dataSource keys are reachable under `data.`; its current text says dataSource carries the adapter, the renderer used to publish it under `data`, and objectui#9308 removed that root. configEvaluationScope in packages/react/src/SchemaRenderer.tsx spreads usePredicateScope() plus current_user, record (conditional) and page — no data, no dataSource.",
        "H2": "Reproduced against the CURRENT renderer (vitest root config, dom project, happy-dom; @object-ui/react aliased to packages/react/src, @object-ui/core to packages/core/src; the SchemaRenderer evaluation memo → ExpressionEvaluator.evaluateCondition). With the real `text` renderer registered: the fence as written → `renders component from schema` passed, `hides component when hidden expression is true` FAILED with `Unable to find an element with the text: Secret`, body an empty div, reporter line `Reason: Failed to evaluate expression \"userRole !== \"admin\"\": userRole is not defined`. Corrected fence → 2 passed (2). Copied verbatim with no renderer registered, both tests fail on `Unknown component type: text` — the guide's own 'Common testing mistakes' section already states that; not this card.",
        "H3": "objectui#9308 is closed completed, option B landed as 852437297b (PR 9369). It DOES change the card's option set: the card's second route (spell the gate against the root the example publishes, i.e. `data.userRole`) is dead — `data: dataSource` is gone from the evaluator scope and schema-expressions.md warns that re-publishing `data` only restores an always-false verdict. The first route (PredicateScopeProvider) stands and is the one taken; the bare `userRole` spelling inside the expression is correct once the host publishes it under that name."
      },
      "files_changed": ["skills/objectui/guides/testing.md"],
      "line_budget": {
        "budget": "+6 net across skills/**",
        "testing_md_whole_file": "364 → 369 (+5)",
        "package_sum": "501 → 506 (+5): skills/objectui/SKILL.md 137 (only SKILL.md under skills/, unchanged) + testing.md 364 → 369",
        "composition": "fence +2 (the `it` block gained a viewer leg), prose +2, blank line +1; no re-wrap, no deletion used as currency",
        "verdict": "within budget"
      },
      "tests": "Scratch reproduction (three files under packages/components/src/__tests__/, deleted before commit): `pnpm exec vitest run` over all three → exit 1, `Test Files 2 failed | 1 passed (3)`, `Tests 3 failed | 3 passed (6)` — as-written/no-registration 2 failed (Unknown component type), as-written/registered 1 failed (Secret not found, `userRole is not defined`), corrected 2 passed. Corrected variant re-run alone with --reporter=verbose → exit 0, both tests ✓, `Test Files 1 passed (1)`. `diff` of the published fence body against the tested file: exactly two hunks (describe name, renderers import). Gates at d1fda66e1a: check:skills-paths exit 0 (88/89 resolve, 1 baselined); check:skill-eval-tokens exit 0; check:control-bytes exit 0 (9180 tracked text files); check:new-line-citations exit 0 (0 new citations, re-run after commit); check-changeset-presence exit 0 (no changeset owed — skills/** in no files[]); check-governed-queue-guard --test exit 3 GOVERNED (expected); ledger readers of skills/objectui/** (check-skill-examples, check-skills-paths, check-skill-eval-tokens, doc-version-claims, layered-read-declared-path-4016, check-shell-escape-residue, check-installed-spec-pin-claims, dollar-dialect-alias-census, markdown-test-inputs tests + skill-guide-provider-envelope) → Test Files 10 passed (10), Tests 365 passed (365). NOT MEASURED: pnpm check:skill-examples — exit 2 PRECONDITION NOT MET (no dist/ in the worktree; its scoped build closure is 30 packages per --dry=text, beyond the foreground cap) — declared to CI (skill-examples.yml runs on every PR, no path filter); this diff adds no marker and touches no marked fence. Not owed: check:doc-snippets / doc-examples / doc-fences (DOCS_ROOT content/docs; skills/** explicitly unclaimed). No ablation: markdown-only change, no dist, no guard.",
      "gates": {
        "check:skills-paths": "exit 0",
        "check:skill-eval-tokens": "exit 0",
        "check:control-bytes": "exit 0",
        "check:new-line-citations": "exit 0 (0 new)",
        "check-changeset-presence": "exit 0, no changeset owed, no skip-changeset label written (objectui: zero readers)",
        "check-governed-queue-guard --test": "exit 3 GOVERNED (Tier H, draft only)",
        "ledger markdown readers (10 test files)": "365/365 passed",
        "check:skill-examples": "NOT MEASURED locally (exit 2 precondition: dist absent, 30-package build closure) — CI",
        "check:doc-snippets / doc-examples / doc-fences": "not owed (roots exclude skills/**)",
        "CI on PR 10941": "in_progress — not waited for"
      },
      "mcp_calls": "0 — no MCP tools called",
      "api_writes": "3 — (1) POST /repos/objectstack-ai/objectstack/dispatches via scripts/pm/fleet-write/dispatch.mjs (relay run 36400463636 executed POST /repos/objectstack-ai/objectui/pulls, draft, as objectstack-fleet[bot]); (2) scripts/pm/label-write.mjs --repo objectstack-ai/objectui --issue 10941 --assign huangyiirene (POST /repos/objectstack-ai/objectui/issues/10941/assignees); (3) scripts/pm/post-stamped.mjs --comment=9380 --repo=objectstack-ai/objectui (POST /repos/objectstack-ai/objectui/issues/9380/comments, this report). Plus 2x git push (not REST). Reads only otherwise (issues/comments/pulls GETs).",
      "deviations": [
        "PM suggested route (`data.userRole` in both the bag prose and the hidden expression) NOT taken: falsified on today's tree — `data` is no longer a renderer root (objectui#9308). Took the card's other route: PredicateScopeProvider; the expression text is unchanged.",
        "A viewer leg (+2 fence lines, via rerender) was added beyond the strict 'minimal text': the test named 'hides component when hidden expression is true' otherwise never exercises its hidden branch; admin assertion kept verbatim, not inverted. Within the +6 budget (+5 used).",
        "check:skill-examples declared to CI (NOT MEASURED locally) — see tests.",
        "objectstack's dispatch-gates.mjs was not used: objectui has no such script; the gate set was hand-derived from objectui package.json, .github/workflows and scripts/markdown-test-inputs.mjs --list.",
        "PR body quotes the vitest verbose reporter with its angle-bracket separator spelled › (sanitizer); stated in the body.",
        "Commit trailers are the model-free pair (Claude-Session URL + Co-authored-by: Claude) per os-dev.md, not the harness reminder's model-named form."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — the verbatim fence (only its own imports, no renderer registered) fails both tests with `Unknown component type: text`; testing.md's 'Common testing mistakes' already names this. Acceptance notes only.",
        "carrier: this PR · noted, not filed — the old fence passed a plain bag as SchemaRendererProvider dataSource, the TS2740 shape under objectui#7912's DataSource typing (objectui#9346 measured it on the docs pages); the corrected fence passes nothing there, so it leaves with this PR.",
        "carrier: none · noted, not filed (observation/naming, not class a|b|c) — grep of skills/** for `dataSource={` after the edit: SKILL.md (the DataSource interface sentence), project-setup.md, auth-permissions.md (`authenticatedDataSource`), data-integration.md (`dataSource`) all inject an adapter; i18n.md's 'Language-aware + responsive page' fence injects an UNDECLARED identifier named `data` as the adapter — misleading name, but that fence gates nothing on its keys and is a fragment, so nothing throws and no reader is misdirected to a root. Every remaining `userRole` gate under skills/** sits under a PredicateScopeProvider-published scope (page-builder.md, auth-permissions.md, data-integration.md) or under schema-expressions.md's explicit 'a host that published `data`' premise. Not in the PR body (dev writes it once); this report only."
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    Patch round 1 for objectui#9380 (PR objectui#10941): the Pattern 5 fence now passes when copied verbatim.

    {
      "issue": 9380,
      "round": "patch 1",
      "status": "done",
      "branch": "claude/issue-9380-testing-pattern5-data-root",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10941",
      "pushed_sha": "fd764036b1385120ee2d8b9c01a51c86935e7c07",
      "previous_sha": "d1fda66e1a89350fb837204d3af833e328921006",
      "session": "session_01EBx9rvB7dufCz4at53x35U",
      "premise_still_valid": true,
      "summary": "One line added to the Pattern 5 fence of skills/objectui/guides/testing.md: `import '@object-ui/components';`, a bare side-effect import in the form page-builder.md uses for its plugin registrations, placed with the other imports. Nothing else in the fence moved; skills/objectui/evals/testing.json untouched. Copied verbatim the fence now passes both tests on the current renderer; the same file minus that import fails both with `Unknown component type:` (the control). New commit on the existing branch; no force-push; PR body not edited.",
      "files_changed": ["skills/objectui/guides/testing.md"],
      "line_budget": {
        "budget": "+6 net across skills/** for the whole PR",
        "testing_md_whole_file": "364 (origin/main) → 369 (d1fda66e1a) → 370 (fd764036b1): +6 total",
        "package_sum": "501 → 507 (+6): skills/objectui/SKILL.md 137 (only SKILL.md under skills/, unchanged) + testing.md 370",
        "this_round": "+1 (the import line), no re-wrap, no deletion used as currency",
        "verdict": "exactly at budget; nothing left"
      },
      "tests": "Scratch pair under packages/components/src/__tests__/ (deleted before commit), run in the vitest `dom` project whose light setup registers nothing, `--reporter=verbose`: scratch A = the published fence body copied verbatim (`diff` against the fence body: exit 0, identical) → ✓ renders component from schema 29ms · ✓ hides component when hidden expression is true 8ms. Control = scratch A minus the one import (`diff`: exactly `import '@object-ui/components';`) → × renders component from schema — `Unable to find an element with the text: Hello World`, dump `Unknown component type:` · × hides component when hidden expression is true — `Unable to find an element with the text: Secret`, dump `Unknown component type:`. Run summary: `Test Files 1 failed | 1 passed (2)`, `Tests 2 failed | 2 passed (4)`, vitest exit 1 (the control's red). Gates at fd764036b1 (exit codes captured before any pipe): check:skills-paths exit 0 (28/28 under skills/); check:skill-eval-tokens exit 0; check:control-bytes exit 0 (9180 tracked text files); check:new-line-citations exit 0, 0 new (re-run after the commit); check-changeset-presence exit 0, no changeset owed; check-governed-queue-guard --test exit 3 GOVERNED (expected); the 10 ledger readers of skills/objectui/** → Test Files 10 passed (10), Tests 365 passed (365). NOT MEASURED locally, unchanged from round 1: check:skill-examples (dist absent, 30-package closure) — CI; the fence is unmarked so it is outside that gate's judged population either way.",
      "gates": {
        "check:skills-paths": "exit 0",
        "check:skill-eval-tokens": "exit 0",
        "check:control-bytes": "exit 0",
        "check:new-line-citations": "exit 0 (0 new)",
        "check-changeset-presence": "exit 0, no changeset owed",
        "check-governed-queue-guard --test": "exit 3 GOVERNED",
        "ledger markdown readers (10 test files)": "365/365 passed",
        "check:skill-examples": "NOT MEASURED locally — CI",
        "CI on PR 10941 at fd764036b1": "in_progress — not waited for"
      },
      "mcp_calls": "0 — no MCP tools called",
      "api_writes": "1 this round — scripts/pm/post-stamped.mjs --comment=9380 --repo=objectstack-ai/objectui (POST /repos/objectstack-ai/objectui/issues/9380/comments, this comment). Plus 1x git push (not REST). No PR body PATCH, no label write, no MCP.",
      "deviations": [
        "Import spelled as the bare side-effect form page-builder.md uses (`import '@object-ui/plugin-grid';`); the guide's Common testing mistakes bullet names the mistake in prose, not a form, so the sibling guide's form was taken.",
        "The PR body still describes the verbatim variant as failing on registration (round-1 wording); per the write-once rule it was not edited — this comment is the corrected record."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: seat (Acceptance note already recorded) · not touched — skills/objectui/evals/testing.json."
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Review — ACCEPT · PR objectui#10941 · Tier H (governed skills/**) ⇒ awaiting a human approval

    domain:devx seat 2 (session_01EBx9rvB7dufCz4at53x35U), reviewed against GitHub at 2026-09-28T09:34Z on head fd764036b1, ⛔ not against the reports (os-dev-report round 1 and patch round 1 on this card).
    Implemented-by: os-dev subagent of this session, branch claude/issue-9380-testing-pattern5-data-root · Reviewed-by: this seat · Contract review at CONTRACT_REVIEW_TIER: PASS on this head (5867268801, isolated at-tier subagent; an earlier PASS on d1fda66e1a escalated the gap this head closes).

    Checklist

    • Shape: draft ✓ · base main ✓ · first line Fixes #9380 ✓ · the only closing keyword ✓ · ## 维护者速读(草稿) present ✓ · assignee huangyiirene ✓.
    • Path face: skills/objectui/guides/testing.md only ✓ — governed ⇒ Tier H; no changeset owed (skills/** ships via npx skills add, in no package's files[]).
    • Skills budget: +6 net against a dispatched +6 (testing.md 364 → 370; package sum 501 → 507) ✓ — exactly at budget, no re-wrap. ⚠️ The PR body still reads +5 / 369 from round 1 (write-once); the true figures are these.
    • The dev corrected this seat's suggested route, recorded here: the dispatch suggested re-rooting the gate under data.userRole; objectui#9308 (852437297b) removed data as an expression root, so that route was dead on arrival. The fence now publishes the scope through PredicateScopeProvider, the channel the sibling guides teach. The seat independently confirmed PredicateScopeProvider is exported from the package entry (index.ts → hooks/index.ts → useExpression.ts).
    • Patch round 1 (from the first contract review's escalation): import '@object-ui/components'; added so the fence passes when copied verbatim; the dev's control without it reads Unknown component type on both tests.
    • Customer value (read from a seat loading the whole package): a published example that threw on copy now passes on copy, for +6 lines.
    • CI on fd764036b1: 39 check runs, 36 success, 3 by-design skips, 0 failures (read by the contract reviewer; Skill Example Check included).
    • Commit trailers model-free ✓. mcp_calls 0.

    Out-of-scope findings: Acceptance notes, none filed — skills/objectui/evals/testing.json eval 1 still names SchemaRendererProvider + mock data source for its visibility leg (no reader is misled into a failing test; no measured wrong answer at a public door); the guide's «Common testing mistakes» first bullet says «plugin packages» where the fence now also depends on @object-ui/components (incomplete, not false; the skills budget is spent). Carrier for both: the next PR that touches this guide.

    Route (Tier H): the PR stays draft; this seat requests review from both authorised approvers and posts the final 维护者速读 on the PR. After an authorised approval, this seat runs the landing pre-checks, flips ready and arms auto-merge; ⛔ it never approves or merges.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions