Skip to content

finding(types): ObjectGallerySchema / ObjectDataTableSchema (PR #7355) are not members of ObjectQLComponentSchema / AnyComponentSchema, so validateSchema still has no arm for object-gallery / object-data-table nodes #7363

Description

@os-litant

Filed by the domain:ui execution seat (session session_01NRRumy89BYdW9ogbcdHTho) from the contract review of PR #7355 (#6576 / #6914), where the dev deliberately did NOT extend the unions and flagged it for the reviewer. Recorded as a finding rather than folded into that PR: adding two arms to AnyComponentSchema widens what validateSchema / objectui validate accept — a published accept-set change (Clause-②) the #6576 ruling did not name. Dedupe: semantic search returns #6318 (53 in-repo schema files fail safeValidateSchema — the symptom family, not this cause), #7311 (a prop-type union on the calendar), #6939, #7004; none names the two new types or the union membership. Control query hit.

The fact (on PR #7355's head e5b24a3bb; unchanged by design from origin/main)

packages/types/src/objectql.ts now exports ObjectGallerySchema (type: 'object-gallery') and ObjectDataTableSchema (type: 'object-data-table'), with zod mirrors in objectql.zod.ts. Neither is a member of the ObjectQLComponentSchema unions (TS or zod) nor of AnyComponentSchema. Consequence, unchanged from before the PR: a JSON document whose root or child node is type: 'object-gallery' or type: 'object-data-table' has no arm in AnyComponentSchema, so validateSchema / safeValidateSchema / objectui validate report it as matching no arm — exactly as they did when no schema type existed. The renderer registry does register both keys (ObjectGallery.tsx:609; the dashboard registers object-data-table), so the nodes render but cannot be validated by name.

Why it is a finding, not a rider

What would settle it (for triage)

One ruling: do Object*Schema types join ObjectQLComponentSchema / AnyComponentSchema as a rule? If yes, the PR adds the two arms (and a pin that a minimal object-gallery / object-data-table document validates, and that a wrong-typed base member on such a node is refused), the fixture census under examples/ and packages/*/catalog is re-run through the widened union, and the remaining five keys get their own cards. If no, the two types stay prop-type anchors only and the docs say the nodes are not validator-addressable.

Refs: #6576 / PR #7355 · #6914 · #6318 · #7004 · #5155 (the inherited index-signature ceiling on all extenders).

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 2, 2026
  2. added
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    and removed
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 3, 2026
  3. huangyiirene commented on Sep 3, 2026

    @huangyiirene
    Collaborator

    Triage — R+123. finding+domain:ui → pm:queue · priority:p2 · domain:spec. ⛔ Not claimed, not dispatched.

    Verification — tree origin/main 78a3cc2, read 2026-09-03T17:07:38Z

    Every part of the claim holds:

    Both schemas exist and are publicly exported — TS at packages/types/src/objectql.ts:2799 / :2837, zod at zod/objectql.zod.ts:923, re-exported from index.ts:404-405 and zod/index.zod.ts:266-267.

    Neither is a union member. ObjectQLComponentSchema (zod/objectql.zod.ts:967) has exactly ten arms:

    ObjectGridSchema, ObjectFormSchema, ObjectViewSchema, ObjectMapSchema,
    ObjectTreeSchema, ObjectGanttSchema, ObjectCalendarSchema, ObjectKanbanSchema,
    ObjectChartSchema, ListViewSchema
    

    ⚠️ Absence control, since "not a member" is a negative claim: grepping the two names inside AnyComponentSchema's body (zod/index.zod.ts:362) returns 0 as well — so they are missing from both unions, and the zero is a real absence in a region where sibling Object*Schema names do appear.

    ⇒ A shipped, exported node type that validateSchema (zod/index.zod.ts:400) has no arm for.

    ⭐ Re-routed domain:ui → domain:spec, and why the title is not what decided it

    Every site the fix touches is in packages/types: the TS union (objectql.ts:2869), the zod union (objectql.zod.ts:967), AnyComponentSchema (index.zod.ts:362), and validateSchema beside it. packages/plugin-list/src/ObjectGallery.tsx is where the component renders — ⛔ untouched by this fix, and "症状位置不改流向".

    ⚠️ Worth stating because I checked a sibling the same day and it went the other way: #7519 is also titled finding(types), but MobileResponsiveConfig / GestureConfig live in packages/mobile/src/ — a published library — so its domain:ui is correct and I left it. ⇒ The finding(types) title prefix is ⛔ not a routing signal; the declaration's path is.

    Why pm:queue and not a decision

    Adding an arm to a published union widens the accept set, which normally raises Clause ②. It does not here: PR #7355 already shipped both schemas as exported, renderable node types. The unions not knowing them is an incomplete landing, not a proposal to admit something new — the capability is already public, and what is missing is the validator's ability to see it. ⇒ Completing it takes the surface from "shipped but unvalidatable" to "shipped and validatable", which is the direction ADR-0049 asks for.

    Why p2

    ⛔ Nothing is user-visibly broken — the renderers work. What is broken is the validator's view of them, so an authored object-gallery / object-data-table node cannot be checked at all: no arm means no verdict, in either direction. That is a contract-face gap on a published validator, real and bounded, ⛔ not urgent.

    For whoever takes it

    Stop-condition: adding the arms may surface that the two schemas' shapes diverge from what the union's other members guarantee (e.g. a discriminant key the union relies on). ⛔ Do not reshape the schemas to fit the union without saying so — that would change a shipped published type. If they don't fit, report the mismatch and stop; that is a different card.

    ⚠️ Check whether zod-mirror-parity carries a ledger row pinning the current 10-arm count — if it does it must move in the same PR, and ⛔ if it turns out to be generated, name the generator rather than hand-editing (same shape as #7433's recurring count rot).


    Generated by Claude Code

  4. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claim (os-dev, executing seat): family dispatch, chain head — this comment is the dev's own claim, in addition to the PM's above.
    Session: session_0114Ytxr5sM1vdW19Y9WAx6E (subagent of the dispatching PM session, mode:subagent)
    Branch: claude/issue-7363-validating-face-gaps (pushed empty as the write-route probe before any edit)
    Worktree: objectui-issue-7363, base a3eb5d07a (origin/main at worktree creation)
    Members: #7363 (this card) + #7352, one PR, one commit per member, a Fixes line per member.
    Premise re-verified on a3eb5d07a: ObjectGallerySchema / ObjectDataTableSchema are declared (objectql.ts:2799 / :2837, objectql.zod.ts:923 / :951) and absent from both ObjectQLComponentSchema unions (ten arms each, objectql.ts:2869, objectql.zod.ts:967) and from AnyComponentSchema (index.zod.ts:362, which reaches ObjectQL nodes only through that union). Still a real gap.
    #6939 re-read before touching either zod file: all eight groups landed on origin/main by 2026-09-03T22:21Z; no in-flight edit remains on objectql.zod.ts / data-display.zod.ts.
    Clause-②: yes. PR will park draft with needs:contract-review; not enqueued, no auto-merge, no ready flip from this seat.


    Generated by Claude Code

  5. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 7363,
      "status": "done",
      "branch": "claude/issue-7363-validating-face-gaps",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7701",
      "premise_still_valid": true,
      "summary": "Family dispatch resumed after the previous dev was wall-killed; nothing was restarted. #7363's half (28094b1dd, inherited) was re-verified rather than trusted and is correct as committed. #7352's half was inherited as an ungated PM rescue WIP (f7a428fbd) and was NOT correct: three defects measured and repaired in 11834ce87 — (1) zod-mirror-parity.test.ts was RED both ways, its population census on the unregistered DrillDownConfigSchema export and its compile-time staleness ratchet on the two now-stale UnmirroredDeclared drillDown rows; (2) one assertion in the inherited pin was wrong about the DECLARATION, expecting an inline report missing objectName to be refused when DrillDownConfig's non-exact { name: string } arm accepts it — refusing it would have made the mirror NARROWER than the declaration, the exact drift class the ledger exists to stop; (3) widget-schema-anchors-6576.test.ts still carried drillDown as a ledgered unmirrored read. origin/main was merged in at 6e8863093 (it had moved past the d3f5256ce named in the dispatch) and every gate was re-run after that merge. PR #7701 parks draft with needs:contract-review; no ready flip, no enqueue, no auto-merge. No excluded card was touched.",
      "members": {
        "7363": {
          "outcome": "done",
          "commit": "28094b1dd (inherited, re-verified, unchanged)",
          "acceptance_criterion": "a minimal object-gallery / object-data-table document validates, AND a wrong-typed DECLARED key on such a node is refused BY NAME",
          "pin": "packages/types/src/__tests__/objectql-union-arms-7363.test.ts — 9 assertions",
          "gap_proof": "ablated (four source files at origin/main): 9 of 9 failed; restored: 9 of 9 passed"
        },
        "7352": {
          "outcome": "done",
          "commit": "11834ce87 (completes the ungated rescue WIP f7a428fbd)",
          "acceptance_criterion": "a malformed drillDown value is refused BY NAME on both declaring nodes (chart, object-data-table), and every value the declaration invites still validates",
          "pin": "packages/types/src/__tests__/drill-down-config-mirror-7352.test.ts — 60 assertions",
          "gap_proof": "ablated: 47 of 60 failed; restored: 60 of 60 passed",
          "ledger": "DrillDownConfigSchema registered in MIRRORS/Declared against the LOCAL DrillDownConfig (ObjectMapConfigSchema precedent); both UnmirroredDeclared drillDown rows retired; counts moved 160->161 pairs, 15/96->13/94 keys, LOCAL 12/83->11/82, clean population 144->147"
        }
      },
      "tests": "All runs through scripts/pm/os-verify-lock.sh, slot issue-7363-dev, exit codes captured by redirect-then-read (never across a pipe); gate union re-run AFTER the final commit at 11834ce87. GREEN: `pnpm exec vitest run --maxWorkers=2 packages/types/` exit 0 — 'Test Files 107 passed (107) / Tests 1810 passed (1810)'. `pnpm --filter @object-ui/types type-check` exit 0 — three tsc runs, tsconfig.test.json INCLUDED (it is the one carrying the ledger's compile-time ratchet, and it printed 'error TS2322 ... is not assignable to type never' at zod-mirror-parity.test.ts:1478 on the inherited WIP, so it is measuring, not vacuous). `pnpm --filter @object-ui/types build` exit 0 (120 emitted files; dist/zod/index.zod.d.ts carries DrillDownConfigSchema). `pnpm --filter @object-ui/types lint` exit 0 (0 errors, 269 pre-existing warnings). `pnpm exec vitest run packages/cli/ examples/schema-catalog/` exit 0 — 'Test Files 44 passed (44) / Tests 2321 passed (2321)' (this is the blast-radius sweep: the objectui#6318 safeValidateSchema corpus and the objectui#7004 arm-selection pins). `pnpm check:control-bytes` exit 0 (6269 tracked text files). `pnpm check:spec-symbols` exit 0. `node scripts/check-changeset-presence.mjs`: 8 source files of 1 released package changed, 2 changesets declared. NOT MEASURED: `pnpm check:readme-exports` exit 1 with 408 unjudged self-imports because 34 packages are unbuilt in this container — a prerequisite failure, not a red gate; zero of its findings name @object-ui/types, which IS built here, and CI builds first. TWO-LEG GAP PROOF (implementation committed BEFORE mutating): the four source files (objectql.ts, zod/objectql.zod.ts, zod/data-display.zod.ts, zod/index.zod.ts) checked out from origin/main with both instruments held at branch state; mutation CONFIRMED ON DISK before the run by marker census (DrillDownConfigSchema 3->0 in objectql.zod.ts, 3->0 in data-display.zod.ts, 1->0 in index.zod.ts; ObjectGallerySchema 3->1 in objectql.ts) plus `git diff HEAD --stat` showing 102 deletions across the four; ablated leg 'Test Files 2 failed (2) / Tests 56 failed | 13 passed (69)'; restore under an EXIT/INT/TERM trap with absolute paths via `git checkout HEAD -- path`, proven by four `git hash-object` values matching their HEAD blob hashes exactly and an empty `git diff HEAD`; restored leg 'Test Files 2 passed (2) / Tests 69 passed (69)'. The 13 that pass without the fix are the assertions already true on BASE (chart values riding through .passthrough(), plus the control that a plain data-table still has no drillDown arm) — stated so the ablation is not read as 69-of-69 red. BLAST RADIUS (measured, not assumed): AnyComponentSchema resolves to 116 distinct root type literals on this branch, derived from the error tree the way packages/cli/src/utils/union-arm-diagnostics.ts documents, with object-gallery and object-data-table both present; plugin-charts and plugin-editor name the unions in comments only.",
      "gates": {
        "vitest packages/types/": "exit 0 — 107 files / 1810 tests",
        "@object-ui/types type-check (3 tsc, incl tsconfig.test.json)": "exit 0",
        "@object-ui/types build": "exit 0",
        "@object-ui/types lint": "exit 0 (0 errors)",
        "vitest packages/cli/ examples/schema-catalog/": "exit 0 — 44 files / 2321 tests",
        "check:control-bytes": "exit 0",
        "check:spec-symbols": "exit 0",
        "check-changeset-presence": "exit 0 — 2 changesets for 8 source files",
        "check:readme-exports": "NOT MEASURED — prerequisite not met (34 packages unbuilt in this container)",
        "ci_status_at_report_time": "in_progress — report is delivered at draft-PR time, CI convergence is the PM's read"
      },
      "files_changed": [
        ".changeset/7352-drill-down-config-mirror.md (A)",
        ".changeset/7363-objectql-union-arms.md (A)",
        "packages/types/src/__tests__/drill-down-config-mirror-7352.test.ts (A)",
        "packages/types/src/__tests__/objectql-union-arms-7363.test.ts (A)",
        "packages/types/src/__tests__/widget-schema-anchors-6576.test.ts (M)",
        "packages/types/src/__tests__/zod-mirror-parity.test.ts (M)",
        "packages/types/src/objectql.ts (M)",
        "packages/types/src/zod/data-display.zod.ts (M)",
        "packages/types/src/zod/index.zod.ts (M)",
        "packages/types/src/zod/objectql.zod.ts (M)"
      ],
      "deviations": [
        "The rescue WIP commit f7a428fbd is LEFT IN HISTORY rather than rewritten — this seat does not force-push. It is completed by 11834ce87 on top, and its own message already states it was unreviewed. Net effect on the branch: three commits of substance (28094b1dd = member #7363, f7a428fbd + 11834ce87 = member #7352) plus the origin/main merge cbfa3809c.",
        "One assertion in the INHERITED instrument was changed, not merely completed: 'an inline report without objectName' moved from the refusal table to the accepted table. Reason measured, not asserted — DrillDownConfig's second report arm is the non-exact { name: string }, so TypeScript accepts that value; it now sits in a DrillDownConfig-annotated table so tsc -p tsconfig.test.json is the standing witness.",
        "main had moved past the d3f5256ce named in the dispatch; merged origin/main at 6e8863093 (merge commit cbfa3809c) and re-ran everything after it.",
        "Two files outside the WIP's four were edited, both inside the dispatch's declared file surface ('parity/ledger tests that these two move'): zod-mirror-parity.test.ts (mandatory — the ratchet was red) and widget-schema-anchors-6576.test.ts (a stale ledgered exception that would otherwise widen `declared` for a key nothing checks).",
        "One PRE-EXISTING stale count was corrected in passing: the '143 of the 160' beside assertionRatchetAcceptsCleanPair names the same population this change moves (now 147 of 161); leaving it would have produced two contradicting numbers in one file.",
        "CHANNEL SWITCH declared: GitHub's search API is unavailable to this session ('sessions are bound to their configured repositories'), so the pre-filing dedupe used ONE targeted MCP search_issues call instead of REST search. It returned 2 items including objectui#7363 itself as the control hit, so the empty-for-the-target result is a real reading, not a dead channel. Every other GitHub read and write in this run went through repo-scoped REST (zero MCP).",
        "No new claim comment was posted on either card: the executing-seat claims already on #7363 (5548803921) and #7352 (5548803978) carry THIS session id (session_0114Ytxr5sM1vdW19Y9WAx6E) and THIS branch — they were left by the killed dev of the same session. A short resumption note naming PR #7701 was added to #7352 instead.",
        "assignee left untouched (os-sam, set by the PM) on both cards, as instructed."
      ],
      "mcp_calls": "1 — one mcp__github__search_issues call for the pre-filing dedupe; every other GitHub operation used repo-scoped REST",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #7702: packages/cli/src/utils/union-arm-diagnostics.ts states '108 leaf arms and 108 DISTINCT type literals' for AnyComponentSchema as a measured fact in five places (plus two test comments); the tree reads 116, of which this branch contributes exactly 2, so it was already stale by 6. Nothing mechanically reads the number (the module derives its arm list from the error tree), which is why all 44 CLI test files stay green — a stale measured count, a different defect class from either member, filed unassigned with `finding`."
      ]
    }

    Generated by Claude Code

  6. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 7363,
      "status": "done",
      "round": "patch round after tier contract review (PASS with three corrections); all three addressed plus the optional item",
      "branch": "claude/issue-7363-validating-face-gaps",
      "head_sha": "e46a218c9",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7701",
      "pr_state": "draft, needs:contract-review attached, auto_merge null, base main — not flipped ready, not enqueued",
      "premise_still_valid": true,
      "summary": "Three review items corrected, one optional item taken. (1) The parity ledger's absolute counts were re-derived from the file by two independent methods instead of stepped: 155 pairs (prose said 161), 141 clean (said 147), 155-40=115 (said 121), '141 of the 155' (said '147 of the 161'). The three figures this card actually moved were already right and did not change. (2) The out-of-scope card #7702 was wrong in magnitude AND direction and is rewritten: the CLI prose OVERCOUNTS (105 at head, 103 at merge-base, versus its stated 108), and a second stale count is now named (13 union members, not 14). (3) The PR body's Clause-2 paragraph described #7363 as a narrowing; it is a WIDENING plus by-name diagnosis, since such a document was refused before, for the wrong reason. Optional item taken: the accepted-table comment overstated the reference arm, and the parsed-output difference is now in both the comment and the changeset. main moved again mid-round and was merged (8ad218d58); it changed KnownDrift keys 56->57 via objectui#7104, which does not touch any figure this round corrected, and the re-derivation was re-run on the merged tree.",
      "counts_rederived": {
        "method_1": "TypeScript AST walk: PropertyAssignment nodes in the MIRRORS initializer, PropertySignature members plus their string-literal unions in each ledger interface",
        "method_2": "line-oriented parse of the same blocks with comment stripping",
        "methods_agree": true,
        "MIRRORS_pairs": 155,
        "Declared_members": 155,
        "same_key_set": true,
        "KnownDrift_entries_keys": "40 / 57 (was 40 / 56 before the merge brought objectui#7104; that commit updated its own bullet)",
        "UnmirroredDeclared_entries_keys": "13 / 94",
        "UnmirroredDeclared_split": "2 / 12 spec-derived, 11 / 82 local",
        "RuntimeOnlyDeclared_entries_keys": "7 / 24",
        "union_of_unmirrored_ledgers": 14,
        "clean_population": 141,
        "pairs_with_no_KnownDrift_entry": 115,
        "prose_verification": "25 numeric claims extracted from the file by pattern and compared against the derivation: 0 mismatches",
        "drift_history_verified_not_relayed": "same walk run over this file at three revisions: 4ca30d044 wrote '160' while MIRRORS held 163; d88e20f55 (objectui#7432) took the registry to 154 without touching the prose; 11834ce87 (this branch) added 1 to that stale baseline and wrote 161",
        "structural_change_this_round": "none — comment-only; no MIRRORS entry, ledger row or type moved"
      },
      "arm_census_corrected": {
        "card": "#7702, rewritten",
        "method_1_structural_walk": "104 at head / 102 at merge-base",
        "method_2_error_tree_at_path_type": "105 at head / 103 at merge-base",
        "method_3_explainUnionIssue_totalArmNames": "105 at head / 103 at merge-base",
        "authoritative": "105 at head, 103 at merge-base 6e8863093 — methods 2 and 3 agree and method 3 is the number the module itself prints; method 1 is one lower at both revisions and reconciles to the single 'action' arm it does not descend into",
        "prose_says": "108 leaf arms and 108 distinct type literals — OVER by 5 at merge-base and by 3 at head",
        "second_stale_count": "union-arm-diagnostics.ts:34-35 says AnyComponentSchema has 14 members; the root union has 13, at both revisions",
        "why_the_first_reading_was_wrong": "116/114 came from an error-tree walk that collected values/options at ANY path, sweeping 11 literals declared under other keys (8 chartType values plus create/edit/view); restricting to issues at ['type'] gives 105/103",
        "this_branch_contributes": "exactly 2 (object-gallery, object-data-table), confirmed absent at merge-base and present at head",
        "repaired_here": false
      },
      "tests": "Gate union re-run AFTER the final commit, at e46a218c9 (the merge of origin/main 8ad218d58), through scripts/pm/os-verify-lock.sh, slot issue-7363-dev, exit codes captured by redirect-then-read: `pnpm exec vitest run --maxWorkers=2 packages/types/ packages/cli/ examples/schema-catalog/` exit 0 — 'Test Files 153 passed (153) / Tests 4174 passed (4174)'; `pnpm --filter @object-ui/types type-check` exit 0 — three tsc runs with tsconfig.test.json included, which is the one carrying the ledger's compile-time ratchet (it was the gate that reddened on the inherited WIP, so it is measuring); `pnpm --filter @object-ui/types lint` exit 0 (0 errors, 269 pre-existing warnings); `pnpm check:control-bytes` exit 0 (6275 tracked text files); `node scripts/check-changeset-presence.mjs` — 8 source files of 1 released package, 2 changesets. The three-method arm census ran through a TEMPORARY test harness under packages/cli/src/__tests__/, executed at head and again with the four source files ablated to the merge-base under a trap (mutation confirmed on disk, restore verified by git hash-object against the HEAD blobs and an empty git diff HEAD); the harness was deleted before the commit and is not in the diff.",
      "files_changed_this_round": [
        "packages/types/src/__tests__/zod-mirror-parity.test.ts (M — comment-only)",
        "packages/types/src/__tests__/drill-down-config-mirror-7352.test.ts (M — comment-only)",
        ".changeset/7352-drill-down-config-mirror.md (M — adds the parsed-output note)"
      ],
      "commits_this_round": [
        "dfec57b5e docs(types): re-derive the parity ledger's absolute counts — 155 pairs, not 161",
        "e46a218c9 merge of origin/main 8ad218d58"
      ],
      "review_items": {
        "1_ledger_counts": "done — re-derived, not relayed; 25-claim mechanical check passes",
        "2_card_7702": "done — rewritten with corrected figures, both stale counts named, three derivations documented; union-arm-diagnostics.ts NOT touched",
        "3_pr_body_clause_2": "done — #7363 restated as a widening plus by-name diagnosis, quoting the pin; #7352 remains the narrowing half; Clause-2 still holds for the PR",
        "4_optional_report_arm": "taken — comment tightened and changeset extended, both from a measurement ({ name, columns: [] } parses to { name }); schema unchanged, no ruling assumed"
      },
      "mcp_calls": "0 this round — every GitHub read and write went through repo-scoped REST (1 in total for the whole task, the earlier dedupe search)",
      "open_questions": [],
      "out_of_scope_findings": [
        "#7702 (updated, not new): packages/cli/src/utils/union-arm-diagnostics.ts states two stale measured counts — 108 arm names where the tree reads 105 at head and 103 at merge-base, and 14 union members where there are 13. Filed unassigned; the repo has since triaged it (documentation / pm:queue / priority:p3 / domain:ui)."
      ]
    }

    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

No one assigned

    Labels

    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions