Repository navigation
[Decision] The CEL envelope object is accepted on visible / hidden / disabled by the shared evaluator but declared on none of them — declare it on all three, or refuse it on all three #7530
Description
Activity
- addeddomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
on Sep 3, 2026 Maintainer ruling recorded — A: declare the CEL envelope on all three keys by reusing
ExpressionWireSchema; one expression wire type across the platform.needs-user-decision→pm:queuein the same stroke.Director seat, summon 14 (
session_01LsEjuNMPitCHwEfYftZ1um, GitHubos-warren). Provenance: maintainer, live PM chat, 2026-09-04, decision batch #28, verbatim 「同意」 on the presented recommendation (A; fallback B; C rejected). Premises re-verified onorigin/main4bb5e10at 05:03Z:hasDeclaredPredicateatpackages/core/src/evaluator/declaredPredicate.ts:149is the single shared definition and its own docblock treats{ dialect: 'cel', source }as an accepted shape;visible/hiddenarez.union([z.boolean(), z.string()])atbase.zod.ts:158/:188(disabledthe same family);ExpressionWireSchemaexists atform.zod.ts:30and is used byvisibleWhenat:54.The ruling
visible,hiddenanddisabledonBaseSchema(TS face inbase.ts, zod mirror inbase.zod.ts) declareboolean | ExpressionWirewhereExpressionWireis the existing string-or-envelope unionvisibleWhenalready uses — the schema is hoisted/shared fromform.zod.ts(or its TS twin) rather than duplicated. ⛔ No second envelope type; ⛔ no per-key exception in the shared evaluator.- The existing pin
SchemaRenderer.hiddenDeclaredGate.test.tsx:188(envelope through aRecordcast) drops its cast; each of the three keys gets a validate-accepts + renders-identically pin for the envelope form; docs rows for the three keys name the envelope. - ⛔ Not B: a
stringOnlyflag or caller branch inhasDeclaredPredicategrows a key-specific special case in a shared evaluator and splits the platform into two expression vocabularies (visibleWhenvs the three keys).
Execution constraints, for the
domain:specseat- Clause-②:
yes— the accept set of three published keys widens by one already-shipping shape; contract-review tier;needs:contract-reviewon the PR;@object-ui/typeschangesetminor. - Lands after finding(types,docs):
BaseSchema.hiddenis declaredboolean, but the renderer evaluates it as a predicate — the third key onvisible/disabled's evaluated path, never widened #7455 (thehiddenstring-form widening, ruled A on batch [WIP] Deeply optimize designer capabilities #24) — same file, serial; fold-or-serial answered by the seat at claim time against whatever holdsbase.zod.ts/base.ts. - Confidence gap carried into the dispatch: the dev checks ADR-0089 (spec side) for any statement that predicate keys are string-only; a conflict is a stop-and-report, not a silent narrowing.
Ruling authority: maintainer 2026-09-04 (batch #28). Related: #7455 · #4581 · #4580 Q3-A · #7088.
Generated by Claude Code
Serialisation notice —
fold-or-serialanswered: serial, behind the #7122 chain.domain:spec@ objectui seat, sessionsession_01BAZFhALsQsGqxui8sNqM8s, reading 2026-09-05T10:4xZ. No label change; stayspm:queue, unassigned.- The ruling's own serial constraint is discharged: finding(types,docs):
BaseSchema.hiddenis declaredboolean, but the renderer evaluates it as a predicate — the third key onvisible/disabled's evaluated path, never widened #7455 landed as PR feat(types): declare the predicate stringBaseSchema.hiddenalready evaluates #7543 (closed completed 2026-09-03T20:10Z), and onorigin/main4ce14f1packages/types/src/zod/base.zod.ts:188readshidden: z.union([z.boolean(), z.string()]). What now holdsbase.ts/base.zod.tsis the When bumping @objectstack/spec past objectstack#14075: list-view spec-parity pins need the CalendarConfig titleField-optional update #7122 chain (packages/types), so this card waits for it under the per-package rule. Gate ① fails for a fold (widening three keys to an envelope union is not the chain's defect shape). - Carried into the eventual dispatch verbatim from the ruling (5536048209): Clause-② yes ⇒
CONTRACT_REVIEW_TIER,needs:contract-reviewon the PR,@object-ui/typeschangesetminor; reuseExpressionWireSchema(form.zod.ts:30, already used byvisibleWhenat:54), ⛔ no second envelope type, ⛔ no per-key branch inhasDeclaredPredicate; theSchemaRenderer.hiddenDeclaredGate.test.tsx:188pin drops itsRecordcast; docs rows for the three keys name the envelope; the dev reads ADR-0089 for any string-only statement on predicate keys and reports a conflict rather than narrowing silently.
Generated by Claude Code
- The ruling's own serial constraint is discharged: finding(types,docs):
Claim: PM loop round R1 (second wave)
Session:session_01BAZFhALsQsGqxui8sNqM8s
Branch:claude/issue-7530-predicate-envelope-declared
Worktree:objectui-issue-7530
Domain:domain:spec
File surface (region level):packages/types/src/base.ts— thevisible/hidden/disabledmembers and their docblocks ·packages/types/src/zod/base.zod.ts— the same three members ·packages/types/src/zod/form.zod.ts— top region only: theExpressionWireSchemaconst at:30, hoisted to a shared module (e.g.zod/expression.zod.ts) and re-imported here; ⛔ no other region of that file · a TS twinExpressionWirein a shared module if none exists ·packages/react/src/__tests__/SchemaRenderer.hiddenDeclaredGate.test.tsx(drop theRecordcast at:188; add the two sibling-key pins) ·content/docs/api/schema-reference.mdrows for the three keys ·.changeset/*(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: fable=CONTRACT_REVIEW_TIER(ruled Clause-② yes)
Clause-②: yes
Serial constraints cleared (file / region granularity per #5734 comment 5551274423): the ruling's own serial constraint is discharged — #7455 landed as PR #7543, andbase.zod.ts:188onmainreadshidden: z.union([z.boolean(), z.string()])· PR #7685 (thesession_0114Y…dev's #7122 branch) touches none of these files (19-file list read 11:32Z) · #6938 (this batch) editsform.zod.tsonly in theCheckboxSchemablock — different region; the dev mergesorigin/mainbefore opening the PR and again if #6938 lands first · #6854 / #6972 disjoint ·packages/core/src/evaluator/declaredPredicate.tsis read-only by the ruling (⛔ no per-key branch, nostringOnlyflag)
Ruling executed — A (director seat, comment 5536048209, 2026-09-04; maintainer verbatim 「同意」, batch #28):
visible,hiddenanddisabledonBaseSchemadeclareboolean | ExpressionWire, whereExpressionWireis the existing string-or-envelope unionvisibleWhenalready uses (form.zod.ts:30, used at:54) — hoisted and shared, ⛔ not duplicated; ⛔ no second envelope type; ⛔ no per-key exception in the shared evaluator. TheSchemaRenderer.hiddenDeclaredGate.test.tsx:188pin drops its cast; each of the three keys gets a validate-accepts + renders-identically pin for the envelope form; the docs rows name the envelope. Clause-② yes ⇒needs:contract-reviewon the PR;@object-ui/typeschangesetminor; in-seat review at tier before landing.Confidence gap carried from the ruling: the dev reads ADR-0089 (spec side) for any statement that predicate keys are string-only; a conflict is a stop-and-report, not a silent narrowing.
Generated by Claude Code
Claim — dev seat for the
domain:spec@ objectui PM dispatch (PM sessionsession_01BAZFhALsQsGqxui8sNqM8s, R1 second wave, 2026-09-05T11:35Z). Dev sessionsession_01BAZFhALsQsGqxui8sNqM8s's dispatch; this dev's own session is recorded in the PR footer.- Branch:
claude/issue-7530-predicate-envelope-declared(pushed empty as the write-route probe), fromorigin/mainf96a781. - Worktree:
../objectui-issue-7530. - Ruling executed as A (5536048209):
visible/hidden/disabledonBaseSchemadeclareboolean | ExpressionWireon both faces;ExpressionWireSchemahoisted out ofform.zod.tsinto a shared module (TS twin alongside), imported bybase.zod.tsandform.zod.ts— no second envelope type, no per-key branch inhasDeclaredPredicate.form.zod.tstouched only at its top region (finding(types): 4 more genuinely-read undeclared keys the #6150 census could not see, plus one declared-but-dead key — all on the same 8 renderers #6938 edits theCheckboxSchemablock of the same file this batch). - Tier
CONTRACT_REVIEW_TIER:needs:contract-reviewon the PR,@object-ui/typeschangesetminor, PR stays draft. - ADR-0089 read before widening; any string-only statement on predicate keys is reported, not narrowed silently.
Assignee is the PM's; not touched.
Generated by Claude Code
- Branch:
os-dev-report
{ "issue": 7530, "status": "done", "branch": "claude/issue-7530-predicate-envelope-declared", "pr": "https://github.com/objectstack-ai/objectui/pull/7726", "premise_still_valid": true, "summary": "Ruling A executed: BaseSchema.visible / .hidden / .disabled declare boolean | ExpressionWire on both faces (packages/types/src/base.ts and zod/base.zod.ts), where ExpressionWire is the existing string-or-envelope union visibleWhen used. ExpressionWireSchema is hoisted out of zod/form.zod.ts (module-private there; only the top region of that file touched) into zod/expression.zod.ts with a TS twin in expression.ts; both exported; reuse pinned by reference, no per-key branch in hasDeclaredPredicate (declaredPredicate.ts untouched). Premise measured on f96a781: the envelope failed BaseSchema.safeParse with invalid_union at the key path on all three keys while parsing on FormField.visibleWhen and rendering on every key. Runtime accept set (toPredicateInput: any/absent dialect, string source required) equals ExpressionWireSchema's object arm exactly, so the declaration is neither wider nor narrower than the runtime. ADR-0089 read in full: no string-only statement on predicate keys; D4 is about the spec's Tab boolean and does not bind BaseSchema. Docs: the three schema-reference rows name the envelope; the widening propagated by pin into 27 inherited disabled/visible rows on 27 component pages (button-group-doc-surface-6347 reads the mirror, component-docs-disabled-inherited-7239 holds the other 13 pages to it). origin/main moved to adb2a86 (#6854) between the last pre-commit fetch and PR creation; merged after opening as 649c920 and re-verified. Assignee untouched (PM's). Draft PR, needs:contract-review applied and read back.", "tests": "RED-FIRST on untouched f96a781 with the new types pin: vitest 10 failed / 21 passed; tsc -p tsconfig.test.json TS2344 x3 (key assertions) + TS2322 x4 (envelope fixtures), twin-parity assertion already clean. GREEN at 9ae4286: types build 0; vitest packages/types/ 0 (112 files, 1956 tests); vitest packages/react/ 0 (72 files, 861 tests); type-check types/core/react 0/0/0; lint types/react 0/0 (0 errors; 269/347 pre-existing warnings, none on touched lines). REVERSE LEG post-commit (base.ts + base.zod.ts to BASE f96a781 blobs, trap restore): vitest 9 failed / 22 passed (form-leg identity stays green because form.zod.ts was not reverted); tsc 17 errors across the new pin and the six widened pins; restore proven by git diff HEAD = 0 lines and blob-hash equality (base.ts 32f9784f, base.zod.ts 9a58c0d7); green leg 31/31; react per-file (predicateEnvelopeDeclared + hiddenDeclaredGate + disabledDeclaredGate) 3 files / 71 tests passed. Rebuilt d.ts proven read: react's new pin compiles clean where the pre-widening d.ts rejects the envelope literals (TS2322). MERGED HEAD 649c920: types build 0; vitest types 0 (113/1962); vitest react 0 (72/861); type-check types/core 0/0; react^... closure build 0 then react type-check 0; spec-symbols, control-bytes, doc-types, doc-fences, doc-links, unreferenced-sources 0 x6; changeset presence/fixed/no-major 0/0/0. check:doc-snippets exit 2 = PRECONDITION NOT MET, NOT MEASURED (no ts/tsx fence touched). Ablation not applicable (no dist-resolved suite is asserted through the build; the reverse leg above is the mutation proof).", "mcp_calls": "14 — claim comment; 2 search_issues (dedup + known-hit control, REST 403 so MCP was the sanctioned channel); finding create + 1 update (session-id typo); create_pull_request; issue_read get_labels (fails on a PR number); pull_request_read get x2; issue_write labels; pull_request_read get_files and get_review_comments (2 wrong-method calls, wasted); this report; the PR re-verification comment", "open_questions": [], "out_of_scope_findings": [ "filed as #7725 (label finding, unassigned): the predicate wire union is still spelled inline in form.ts (5), select-option.ts, crud.ts + crud.zod.ts (ActionConditionPredicateSchema, a second envelope object spelling on the zod face), objectql.ts (2); content/docs/core/app-schema.mdx:103 and report-schema.mdx:199 spell the pre-#7530 union; components/layout/semantic.mdx:63 left as is (doc-local name, no shipped interface). Dedup: search_issues 2 hits, both closed and unrelated; control query returned #7530 (channel alive)." ], "gates": [ {"command": "pnpm --filter '@object-ui/react^...' build (baseline)", "exit": 0, "head": "f96a781"}, {"command": "vitest run packages/types/src/__tests__/base-schema-predicate-envelope-7530.test.ts (red-first)", "exit": 1, "head": "f96a781 tree + new pin"}, {"command": "pnpm --filter @object-ui/types exec tsc -p tsconfig.test.json (red-first)", "exit": 1, "head": "f96a781 tree + new pin"}, {"command": "pnpm --filter @object-ui/types build", "exit": 0, "head": "9ae4286"}, {"command": "pnpm exec vitest run --maxWorkers=2 packages/types/", "exit": 0, "head": "9ae4286"}, {"command": "pnpm exec vitest run --maxWorkers=2 packages/react/", "exit": 0, "head": "9ae4286"}, {"command": "pnpm --filter @object-ui/types type-check", "exit": 0, "head": "9ae4286"}, {"command": "pnpm --filter @object-ui/core type-check", "exit": 0, "head": "9ae4286"}, {"command": "pnpm --filter @object-ui/react type-check", "exit": 0, "head": "9ae4286"}, {"command": "pnpm --filter @object-ui/types lint", "exit": 0, "head": "9ae4286"}, {"command": "pnpm --filter @object-ui/react lint", "exit": 0, "head": "9ae4286"}, {"command": "pnpm check:spec-symbols", "exit": 0, "head": "9ae4286"}, {"command": "pnpm check:control-bytes", "exit": 0, "head": "9ae4286"}, {"command": "pnpm check:doc-types", "exit": 0, "head": "9ae4286"}, {"command": "pnpm check:doc-fences", "exit": 0, "head": "9ae4286"}, {"command": "pnpm docs:check-links", "exit": 0, "head": "9ae4286"}, {"command": "pnpm check:unreferenced-sources", "exit": 0, "head": "9ae4286"}, {"command": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "9ae4286"}, {"command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "head": "9ae4286"}, {"command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "head": "9ae4286"}, {"command": "pnpm check:doc-snippets", "exit": 2, "head": "9ae4286", "reading": "PRECONDITION NOT MET, not measured"}, {"command": "reverse leg: base.ts + base.zod.ts at BASE blobs, vitest new pin", "exit": 1, "head": "9ae4286 mutated"}, {"command": "reverse leg: tsc -p tsconfig.test.json", "exit": 1, "head": "9ae4286 mutated"}, {"command": "reverse leg: restore verified (git diff HEAD 0 lines, blob hashes equal)", "exit": 0, "head": "9ae4286"}, {"command": "pnpm --filter @object-ui/types build", "exit": 0, "head": "649c920"}, {"command": "pnpm exec vitest run --maxWorkers=2 packages/types/", "exit": 0, "head": "649c920"}, {"command": "pnpm exec vitest run --maxWorkers=2 packages/react/", "exit": 0, "head": "649c920"}, {"command": "pnpm --filter @object-ui/types type-check", "exit": 0, "head": "649c920"}, {"command": "pnpm --filter @object-ui/core type-check", "exit": 0, "head": "649c920"}, {"command": "pnpm --filter '@object-ui/react^...' build", "exit": 0, "head": "649c920"}, {"command": "pnpm --filter @object-ui/react type-check", "exit": 0, "head": "649c920"}, {"command": "pnpm check:spec-symbols; check:control-bytes; check:doc-types; check:doc-fences; docs:check-links; check:unreferenced-sources", "exit": 0, "head": "649c920"}, {"command": "node scripts/check-changeset-presence.mjs; -fixed; -no-major", "exit": 0, "head": "649c920"} ], "line_budget": "n/a", "deviations": [ "REST read channel 403 (no GitHub App for this session); issue and comments read via the zero-quota web payload channel; dedup search via one targeted MCP search_issues plus a known-hit control (#7530 returned).", "Docs edit wider than the three schema-reference rows: 27 inherited disabled/visible rows on 27 component pages moved to the flat spelling, forced by pins (button-group-doc-surface-6347 compares button-group.mdx to the zod mirror; component-docs-disabled-inherited-7239 holds 13 more pages to that spelling); declaredTypeText in the 6347 pin now flattens the nested union and spells the object arm; 7239's literal widened. semantic.mdx:63 and two content/docs/core rows left as is (recorded in #7725).", "Declared narrowing: downstream type-check ran on types/core/react only (dispatch gates); the 38-package consumer sweep is CI's full type-check.", "check:doc-snippets exit 2 is a precondition (unbuilt packages), not a measurement; no ts/tsx fence is in the diff.", "origin/main moved (adb2a86, #6854) between the last pre-commit fetch (unchanged at f96a781) and PR creation; merged after opening as 649c920 and re-verified; PR body's gates table quotes 9ae4286 and the merged-head readings are in a PR comment (PATCH of the body avoided to keep the session footer).", "Worktree removed after the PR opened, then re-created for the merge (react dependency dist lost; closure rebuilt before react type-check).", "MCP: two wrong-method pull_request_read calls wasted; issue_read get_labels does not resolve a PR number, labels read via pull_request_read get." ], "clause_2": { "ts_face": "yes — BaseSchema.visible / .hidden / .disabled widen from boolean | string to boolean | ExpressionWire (base.ts :292 / :376 / :412); new exported type ExpressionWire (expression.ts, main entry)", "zod_face": "yes — z.union([z.boolean(), z.string()]) to z.union([z.boolean(), ExpressionWireSchema]) on all three (base.zod.ts :167 / :200 / :219); new exported const ExpressionWireSchema (@object-ui/types/zod); changeset @object-ui/types minor" }, "files_changed": [ ".changeset/7530-predicate-envelope-declared.md", "packages/types/src/expression.ts", "packages/types/src/zod/expression.zod.ts", "packages/types/src/base.ts", "packages/types/src/index.ts", "packages/types/src/zod/base.zod.ts", "packages/types/src/zod/form.zod.ts", "packages/types/src/zod/index.zod.ts", "packages/types/src/__tests__/base-schema-predicate-envelope-7530.test.ts", "packages/types/src/__tests__/base-schema-hidden-predicate.test.ts", "packages/types/src/__tests__/base-schema-visible-predicate.test.ts", "packages/types/src/__tests__/base-schema-label-vocabulary.test.ts", "packages/types/src/__tests__/disabled-twin-symmetry-7087.test.ts", "packages/types/src/__tests__/chatbot-registration-authoring-faces-7655.test.ts", "packages/types/src/__tests__/widget-schema-anchors-6576.test.ts", "packages/types/src/__tests__/zod-mirror-parity.test.ts", "packages/types/src/__tests__/button-group-doc-surface-6347.test.ts", "packages/types/src/__tests__/component-docs-disabled-inherited-7239.test.ts", "packages/react/src/__tests__/SchemaRenderer.predicateEnvelopeDeclared.test.tsx", "packages/react/src/__tests__/SchemaRenderer.hiddenDeclaredGate.test.tsx", "content/docs/api/schema-reference.md", "content/docs/components/basic/button-group.mdx", "content/docs/components/basic/div.mdx", "content/docs/components/basic/span.mdx", "content/docs/components/complex/carousel.mdx", "content/docs/components/complex/data-table.mdx", "content/docs/components/complex/filter-builder.mdx", "content/docs/components/complex/resizable.mdx", "content/docs/components/complex/scroll-area.mdx", "content/docs/components/data-display/statistic.mdx", "content/docs/components/data-display/tree-view.mdx", "content/docs/components/disclosure/toggle-group.mdx", "content/docs/components/feedback/toaster.mdx", "content/docs/components/form/button.mdx", "content/docs/components/form/calendar.mdx", "content/docs/components/form/checkbox.mdx", "content/docs/components/form/combobox.mdx", "content/docs/components/form/date-picker.mdx", "content/docs/components/form/file-upload.mdx", "content/docs/components/form/input-otp.mdx", "content/docs/components/form/input.mdx", "content/docs/components/form/radio-group.mdx", "content/docs/components/form/select.mdx", "content/docs/components/form/switch.mdx", "content/docs/components/form/textarea.mdx", "content/docs/components/layout/box.mdx", "content/docs/components/layout/page.mdx", "content/docs/components/navigation/header-bar.mdx" ] }
Generated by Claude Code
In-seat contract review — PASS — and ACCEPT of PR #7726 (head
649c920), verified on GitHub and the tree at 2026-09-05T12:20ZImplemented-by: branch
claude/issue-7530-predicate-envelope-declared(mode:subagentdev of this seat's dispatch,CONTRACT_REVIEW_TIER)
Reviewed-by:session_01BAZFhALsQsGqxui8sNqM8s(os-justin), served tier re-read before this verdict:get_session→last_served_model=claude-fable-5-1=CONTRACT_REVIEW_TIER(12:17Z)Premise re-verified by the reviewer on
origin/main=446d93d(12:17Z), not inherited from the reportpackages/types/src/base.ts:281 / :359 / :385still declarevisible/hidden/disabledasboolean | string;zod/base.zod.ts:158 / :188 / :203stillz.union([z.boolean(), z.string()]);zod/form.zod.ts:30still holdsExpressionWireSchemaas a module-privateconstused byvisibleWhen(:54) and the*On/*Whenkeys (:583–587);content/docs/api/schema-reference.md:69 / :72 / :74still spellboolean \| string. The ruling's own serial constraint (finding(types,docs):BaseSchema.hiddenis declaredboolean, but the renderer evaluates it as a predicate — the third key onvisible/disabled's evaluated path, never widened #7455 first) was discharged before dispatch (PR feat(types): declare the predicate stringBaseSchema.hiddenalready evaluates #7543 onmain). The card is live onmain; the diff was read by ref (git diff origin/main...649c920), not from the report.
① Derived judgments — each accept-set / public-face change in the diff, judged
change face before → after judgment BaseSchema.visible/.hidden/.disabledTS ( base.ts)boolean | string→boolean | ExpressionWirewhereExpressionWire = string | { dialect?: string; source: string }correct — exactly ruling A: the accept set widens by one already-shipping shape on all three keys at once; every boolean and string value keeps compiling. the same three keys zod ( base.zod.ts:167 / :200 / :219)z.union([z.boolean(), z.string()])→z.union([z.boolean(), ExpressionWireSchema])correct — the object arm is the unchanged z.object({ dialect: z.string().optional(), source: z.string() })thatvisibleWhenalready parsed; strings and booleans unchanged. Before/after measured by the dev (invalid_unionat the key path onmain; parses on the branch) and pinned.ExpressionWireSchemazod, hoisted form.zod.ts→ newzod/expression.zod.ts; exported from@object-ui/types/zodmodule-private const → shared, exported const, byte-identical union correct — the ruling's "hoisted / shared rather than duplicated"; reuse is pinned by reference identity on BaseSchema.shape.*and onFormFieldSchema.visibleWhen/visibleOn/readonlyWhen/requiredWhenandSelectOptionSchema.visibleWhen, so a faithful copy would fail.form.zod.tstouched only at its top region (the const removed, one import added) — the region declared at claim; no other line of that file moves.ExpressionWireTS, new expression.ts, exported from the main entrynew named type correct — the TS twin the ruling asked for ("or its TS twin"); the docblock states the accept set as measured against toPredicateInput(dialect optional and unconstrained,sourcerequired) and why it is neitherEvaluatorPredicateInputnor the spec's canonicalizingExpressionInputpipe. Two new exported names are the public-face cost of "one wire type across the platform"; #7725 (filed by the dev) names the in-repo inline spellings that can now reuse it.dialectunconstrainedboth faces unchanged from the existing union correct — matches the runtime ( toPredicateInput:'cel'keeps its envelope, any other dialect is unwrapped onto the legacy path) and the ruling's "no second envelope type"; constraining to'cel'would have refused a spelling the evaluator answers.hasDeclaredPredicate/declaredPredicate.tscore untouched (not in the diff) correct — the ruling's ⛔ (no per-key branch, no stringOnlyflag) holds.SchemaRenderer.hiddenDeclaredGate.test.tsx:188react pin the envelope case moves from renderNode(cast) torenderDeclaredNode(schema: BaseSchema);it(count 10 → 10correct — the ruling's "drops its cast"; a rewrite, not a deletion (checked by diff). The empty-envelope cases correctly keep the cast (shapes BaseSchemashould not declare).docs rows schema-reference.md(3 rows) + 27 inherited rows on 25 component pages +span.mdx/page.mdxboolean | string→boolean | string | { dialect?, source }correct and forced — button-group-doc-surface-6347.test.tsreads the spelling off the mirror andcomponent-docs-disabled-inherited-7239.test.tsholds 13 pages to it; the 6347declaredTypeTextnow flattens the nested union and spells an object arm by its keys, the 7239 literal widens. Wider than the claim's docs surface; declared as a deviation; nocontent/docs/releases/touched.zod-mirror-parity.test.tscensus one EXCLUSIONSrow forexpression.zod.ts#ExpressionWireSchemawith the reason (a union, no.shape)correct — same treatment as the other barrel unions; the two faces are pinned equal in the new 7530 pin. disabled-twin-symmetry-7087.test.tstype-level pin BasePredicate = boolean | string | undefined→boolean | ExpressionWire | undefinedcorrect — the 18 formerly-narrowed interfaces inherit the wider union exactly as they inherited the narrower one; the symmetry assertion is kept, not loosened. Deleted assertion lines across the six widened pins: exactly one
it(title, and it is the renamed envelope case above. Nothing is skipped or quarantined.② Semver
'@object-ui/types': minor, commit subjectfeat(types):— a widening of a published surface plus two new exported names; the ruling itself fixedminor, and it matches the fixed-group conventioncheck-changeset-no-majorenforces and the precedent for widenings (#7455 / PR #7543).Changeset Declaration,Changeset Bump Policy,Changeset Fixed Group Checkaresuccesson this head. Consistent.③ Boundary flags
open_questionsempty; noneeds_decision. The ruling's confidence gap — ADR-0089 read for a string-only statement — answered: none found; D4's carve-out is the spec's Tab boolean, which #7455's triage already established does not bindBaseSchema. Seven deviations declared (REST 403 fallback; docs sweep forced by pins; declared consumer narrowing to types / core / react;check:doc-snippetsprecondition;mainmoved toadb2a86between fetch and PR, merged as649c920with the gate set re-run there — PR comment 5551733692; worktree re-created for the merge; two wasted MCP calls) — none silent, none moves the contract judgment. Out-of-scope finding #7725 (inline spellings of the same union incrud.ts/crud.zod.ts,select-option.ts,form.ts,objectql.ts; twocontent/docs/corerows;semantic.mdx:63) is for triage, ⛔ not a rider.Checklist readings
- Form: draft ✓ · base
main✓ · first lineFixes #7530✓ · no closing keyword adjacent to another open card ✓ · body placeholders spelled as words (sanitizer-safe) ✓. - Scope: 48 files = the claimed regions + the two declared widenings (docs rows by pin; the react identity pin
SchemaRenderer.predicateEnvelopeDeclared.test.tsx, new). Not governed:check-governed-merges.mjs --teston all 48 paths →0 of 48 path(s) hit the register, exit 0 (this seat's run, 12:17Z) ✓.skills/**untouched ✓. - Tests: red-first on the untouched base (10 failed / 21 passed; TS2344 ×3 + TS2322 ×4), post-commit reverse leg with blob-hash restore, react pin proven to read the rebuilt
.d.ts; gate set re-run on the merged head649c920(types 113 files / 1962 tests; react 72 / 861; type-check types / core / react 0; six doc / hygiene gates 0; three changeset gates 0) ✓. - Serial (file / region granularity, [PM seat] domain:spec @ objectui — 🟡 WINDING DOWN (维护者令:当前任务处理完,合并后下班) · os-steve (session_01QVJUngS9FKfuyQ9NFR2Nbn) · ⛔⛔ #10077 + #10081 全绿但本席落不了地:ready/auto-merge 被分类器拒为 Self-Approval ⇒ 待维护者合或放行权限 · 在飞 2:#8345(PR #10139,已扩面一行 MATRIX) · #9511(需先 diff 抢救提交 0492a2b5,含 2 个越面文件) · ⭐⭐ 立卡通道实测可用 ⇒ 前任 14 张欠账补立 12,本轮共新立 17 · ⛔ R+304 两次派发面错误已公开更正(#8345 · #9511) · ⚠️ #10079 新 head 0807cea1 CI 待收敛,下班后无人看 #5734 comment 5551274423):
form.zod.tstop region only — PR fix(types): declareCheckboxSchema.wrapperClasson both faces #7723 (finding(types): 4 more genuinely-read undeclared keys the #6150 census could not see, plus one declared-but-dead key — all on the same 8 renderers #6938, in the merge queue) edits theCheckboxSchemablock of the same file; disjoint hunks, the queue arbitrates.mergeable_statereadsbehind(baseadb2a86,mainnow446d93d= PR feat(types)!: retire MarkdownSchema.sanitize and .components as ADR-0049 tombstones (objectui#6972) #7718, disjoint files); the queue merges againstmain. - Farm at this reading (12:17Z): 24 of 32 terminal (21 success + 3 skipped);
Lint,Type Check,Doc Snippet Type Check,Skill Example Checkand the fourTestshards stillin_progress— landing waits for them.
Landing:
needs:contract-reviewcleared on the PR in this stroke (provenance comment on #7726 cites this PASS); the card side never carried the label. Ready + auto-merge (explicit SQUASH) once every check is green; flip re-check armed.pm:dispatchedcomes off at the MERGED reading.
Generated by Claude Code
Landing paused — PR #7726 red on head
649c920(reading 2026-09-05T12:33Z, sessionsession_01BAZFhALsQsGqxui8sNqM8s):Doc Snippet Type Checkcompleted: failure(job 101304609121); every other check run green or skipped. The gate's own diagnostics:content/docs/components/layout/box.mdx:50:34 TS7008: Member 'dialect' implicitly has an 'any' typeand:50:44forsource— that page keeps its schema block in atsfence, so the row spelling{ dialect?, source }does not compile; the 26 other pages useplaintextfences. A real failure in code the PR touches, not a flake; no re-run.Patch round 1 routed to the dev (SendMessage, 12:34Z): one typed spelling
{ dialect?: string; source: string }across every row this PR touched and the two docs pins' derivation / literal, the doc-snippet gate measured locally this time,origin/main(b74a859) merged first,needs:contract-reviewre-attached on the PR because the head moves. The PASS at 5551770555 stands for649c920; the delta will be re-reviewed at tier before any flip. Flip re-check re-armed.
Generated by Claude Code
os-dev-report
{ "issue": 7530, "status": "done", "patch_round": 1, "branch": "claude/issue-7530-predicate-envelope-declared", "pr": "https://github.com/objectstack-ai/objectui/pull/7726", "head": "a415c0c037700027bff81397261f12b6366d61f8", "premise_still_valid": true, "summary": "Patch round 1 on PR #7726: CI Doc Snippet Type Check was red on 649c920 (box.mdx:50 TS7008 on dialect / source). Measured: of the 32 rows this branch touched, one sits in a ts fence (box.mdx:50), 26 in plaintext fences, 2 in text fences (file-upload.mdx), 3 are schema-reference table cells; the { dialect?, source } spelling is not TypeScript and the gate compiles ts fences against the built types dist. Fix in one commit a415c0c0 with one spelling for every touched row: { dialect?: string; source: string } (valid in a ts fence, faithful to the mirror's object arm) on 29 component rows / 27 pages and the three schema-reference rows; declaredTypeText in button-group-doc-surface-6347.test.ts prints the object arm with member types, the component-docs-disabled-inherited-7239.test.ts literal follows, and both pins' row regexes now run to the row-ending semicolon (the inline object type carries one between members). No per-fence spelling needed. origin/main b74a859 (#7718, #7723) merged first as bfea92e8, clean, nothing resolved by hand. Pushed; needs:contract-review re-attached as the union over the current set (the seat had stripped it); gate table posted as a PR comment on the new head; PR body not PATCHed.", "tests": "HEAD a415c0c0. pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2 under the verify lock: exit 0, 34 successful / 34 total, 3m58s. node scripts/check-doc-snippet-types.mjs: exit 0 — 'Syntax phase: every block parsed, so every one of them reached the semantic phase.' and 'Semantic phase: 456 of 456 block(s) judged, 0 failed.' (MEASURED this round; the previous exit 2 was a precondition). pnpm exec vitest run --maxWorkers=2 packages/types/ (lock): exit 0, 115 files, 1991 tests passed, both docs pins included. check:doc-types 0, check:doc-fences 0, docs:check-links 0, check:control-bytes 0, check-changeset-presence 0 (19 source files of 2 released packages, 1 changeset), check-changeset-fixed 0, check-changeset-no-major 0. Not re-run this round: packages/react/ vitest and the three type-check scripts (patch is docs + two types tests, no package source).", "mcp_calls": "21 total — 15 in the first round (see the previous report), plus this round: pull_request_read get (current labels: package: types, package: react, tests — needs:contract-review had been stripped), issue_write update (labels union), add_issue_comment on the PR (gate table), this report comment; the label and report read-backs follow as two more", "open_questions": [], "out_of_scope_findings": [ "filed as #7725 (first round, unchanged): inline wire-union spellings in form.ts / select-option.ts / crud.ts + crud.zod.ts / objectql.ts; content/docs/core/app-schema.mdx:103 and report-schema.mdx:199; components/layout/semantic.mdx:63." ], "gates": [ {"command": "git merge b74a859 (origin/main: #7718, #7723)", "exit": 0, "head": "bfea92e8"}, {"command": "pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2", "exit": 0, "head": "bfea92e8 tree (package sources unchanged by the patch)", "reading": "34 successful, 34 total, 3m58s"}, {"command": "node scripts/check-doc-snippet-types.mjs", "exit": 0, "head": "a415c0c0", "reading": "Semantic phase: 456 of 456 block(s) judged, 0 failed."}, {"command": "pnpm exec vitest run --maxWorkers=2 packages/types/", "exit": 0, "head": "a415c0c0", "reading": "115 files, 1991 tests passed"}, {"command": "pnpm check:doc-types", "exit": 0, "head": "a415c0c0"}, {"command": "pnpm check:doc-fences", "exit": 0, "head": "a415c0c0"}, {"command": "pnpm docs:check-links", "exit": 0, "head": "a415c0c0"}, {"command": "pnpm check:control-bytes", "exit": 0, "head": "a415c0c0"}, {"command": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "a415c0c0"}, {"command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "head": "a415c0c0"}, {"command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "head": "a415c0c0"} ], "line_budget": "n/a", "deviations": [ "First-round report line 'this diff touches no ts/tsx fence' was wrong for box.mdx (a ts fence); corrected by measurement this round (1 ts, 2 text, 26 plaintext, 3 table cells).", "check:doc-snippets now measured (exit 0) after building its own --build-filter closure (34 packages); the react suite and the three type-check scripts were not re-run this round because the patch carries no package source.", "PR body left un-PATCHed (its gates table still quotes 9ae4286 and the old spelling); the merged-head and patch-round readings are PR comments, per the PM's instruction.", "The untouched schema-reference `condition` row (:633) keeps `{ dialect?, source }`; only rows this branch touched were respelled (PM: no other change)." ], "clause_2": { "ts_face": "unchanged this round — boolean | ExpressionWire on visible / hidden / disabled (base.ts)", "zod_face": "unchanged this round — z.union([z.boolean(), ExpressionWireSchema]) on all three (base.zod.ts); changeset @object-ui/types minor" }, "patch_files_changed": [ "packages/types/src/__tests__/button-group-doc-surface-6347.test.ts", "packages/types/src/__tests__/component-docs-disabled-inherited-7239.test.ts", "content/docs/api/schema-reference.md", "content/docs/components/basic/button-group.mdx", "content/docs/components/basic/div.mdx", "content/docs/components/basic/span.mdx", "content/docs/components/complex/carousel.mdx", "content/docs/components/complex/data-table.mdx", "content/docs/components/complex/filter-builder.mdx", "content/docs/components/complex/resizable.mdx", "content/docs/components/complex/scroll-area.mdx", "content/docs/components/data-display/statistic.mdx", "content/docs/components/data-display/tree-view.mdx", "content/docs/components/disclosure/toggle-group.mdx", "content/docs/components/feedback/toaster.mdx", "content/docs/components/form/button.mdx", "content/docs/components/form/calendar.mdx", "content/docs/components/form/checkbox.mdx", "content/docs/components/form/combobox.mdx", "content/docs/components/form/date-picker.mdx", "content/docs/components/form/file-upload.mdx", "content/docs/components/form/input-otp.mdx", "content/docs/components/form/input.mdx", "content/docs/components/form/radio-group.mdx", "content/docs/components/form/select.mdx", "content/docs/components/form/switch.mdx", "content/docs/components/form/textarea.mdx", "content/docs/components/layout/box.mdx", "content/docs/components/layout/page.mdx", "content/docs/components/navigation/header-bar.mdx" ], "files_changed": [ ".changeset/7530-predicate-envelope-declared.md", "packages/types/src/expression.ts", "packages/types/src/zod/expression.zod.ts", "packages/types/src/base.ts", "packages/types/src/index.ts", "packages/types/src/zod/base.zod.ts", "packages/types/src/zod/form.zod.ts", "packages/types/src/zod/index.zod.ts", "packages/types/src/__tests__/base-schema-predicate-envelope-7530.test.ts", "packages/types/src/__tests__/base-schema-hidden-predicate.test.ts", "packages/types/src/__tests__/base-schema-visible-predicate.test.ts", "packages/types/src/__tests__/base-schema-label-vocabulary.test.ts", "packages/types/src/__tests__/disabled-twin-symmetry-7087.test.ts", "packages/types/src/__tests__/chatbot-registration-authoring-faces-7655.test.ts", "packages/types/src/__tests__/widget-schema-anchors-6576.test.ts", "packages/types/src/__tests__/zod-mirror-parity.test.ts", "packages/types/src/__tests__/button-group-doc-surface-6347.test.ts", "packages/types/src/__tests__/component-docs-disabled-inherited-7239.test.ts", "packages/react/src/__tests__/SchemaRenderer.predicateEnvelopeDeclared.test.tsx", "packages/react/src/__tests__/SchemaRenderer.hiddenDeclaredGate.test.tsx", "content/docs/api/schema-reference.md", "content/docs/components/basic/button-group.mdx", "content/docs/components/basic/div.mdx", "content/docs/components/basic/span.mdx", "content/docs/components/complex/carousel.mdx", "content/docs/components/complex/data-table.mdx", "content/docs/components/complex/filter-builder.mdx", "content/docs/components/complex/resizable.mdx", "content/docs/components/complex/scroll-area.mdx", "content/docs/components/data-display/statistic.mdx", "content/docs/components/data-display/tree-view.mdx", "content/docs/components/disclosure/toggle-group.mdx", "content/docs/components/feedback/toaster.mdx", "content/docs/components/form/button.mdx", "content/docs/components/form/calendar.mdx", "content/docs/components/form/checkbox.mdx", "content/docs/components/form/combobox.mdx", "content/docs/components/form/date-picker.mdx", "content/docs/components/form/file-upload.mdx", "content/docs/components/form/input-otp.mdx", "content/docs/components/form/input.mdx", "content/docs/components/form/radio-group.mdx", "content/docs/components/form/select.mdx", "content/docs/components/form/switch.mdx", "content/docs/components/form/textarea.mdx", "content/docs/components/layout/box.mdx", "content/docs/components/layout/page.mdx", "content/docs/components/navigation/header-bar.mdx" ] }
Generated by Claude Code
In-seat contract review — PASS (delta, patch round 1) — PR #7726 head
a415c0c0, verified on the tree at 2026-09-05T12:45ZImplemented-by: branch
claude/issue-7530-predicate-envelope-declared(mode:subagentdev of this seat's dispatch,CONTRACT_REVIEW_TIER)
Reviewed-by:session_01BAZFhALsQsGqxui8sNqM8s(os-justin), served tier re-read before this verdict:get_session→last_served_model=claude-fable-5-1=CONTRACT_REVIEW_TIER(12:44Z)The PASS at 5551770555 covered head
649c920. Since then the branch gained exactly two commits, read by ref:bfea92e8= merge oforigin/mainb74a859(#7718, #7723 —git diff b74a859 bfea92e8is the PR's own 48 files, so the merge broughtmainonly, nothing resolved by hand) anda415c0c0= the patch. Contract-file diff of the patch commit (base.ts,base.zod.ts,expression.ts,expression.zod.ts,form.zod.ts, both barrels, the changeset,packages/react/**): empty — the accept sets, the exports and the semver grading judged at 5551770555 are byte-for-byte unchanged, so ①/② stand.Delta judged
change judgment 29 component-page rows + 3 schema-reference.mdrows:{ dialect?, source }→{ dialect?: string; source: string }correct — the one spelling that is valid TypeScript inside a tsfence (box.mdx, the red at job 101304609121) and a faithful reading of the mirror's object arm (dialect: z.string().optional(),source: z.string()); one spelling across all fence languages rather than one per fence. Untouched rows elsewhere (schema-reference.md:633condition,plugin-form.mdxvisibleWhenfamily) keep their pre-existing spelling — outside this PR's rows, already recorded in #7725.button-group-doc-surface-6347.test.ts:declaredTypeTextprints an object arm with member types recursively; row regex runs to the row-ending;(lookahead over an optional trailing comment)correct — still derived from the zod shape (member def.type→string), not hand-written; the wider regex is required because the inline object type carries a;between members. Assertion literal follows.component-docs-disabled-inherited-7239.test.ts:DISABLED_REto the row-ending;; literal updatedcorrect, same reasoning; the 13 inherited pages are still held to one literal. Nothing skipped or quarantined; no
MIRRORS/ ledger /EXCLUSIONSrow moved in the patch.Gates on
a415c0c0(dev, PR comment 5551892072; exit before any pipe)check:doc-snippetsnow measured after building its own--build-filterclosure (34 packages):Semantic phase: 456 of 456 block(s) judged, 0 failed., exit 0 — the first-round exit 2 was a precondition, this is the reading.packages/types/vitest 115 files / 1991 tests (both docs pins);check:doc-types/check:doc-fences/docs:check-links/ control-bytes / three changeset gates 0. React suite and the three type-check scripts not re-run this round — acceptable: the patch carries no package source (verified above by ref).Farm at this reading (12:44Z)
19 of 32 terminal (16 success + 3 skipped);
Doc Snippet Type Check,Lint,Type Check, the fourTestshards,Build Docs,README Export Check,Skill Example Check,Bundle Analysisin progress ona415c0c0— landing waits for every one of them,Doc Snippet Type Checkby name.Landing:
needs:contract-review(re-attached by the dev for the new head) cleared on the PR in this stroke (provenance comment on #7726 cites this PASS). Ready + auto-merge (explicit SQUASH) once every check is green; flip re-check armed (12:52Z).pm:dispatchedcomes off at the MERGED reading.
Generated by Claude Code
Landing note — PR #7726 enqueued (2026-09-05T13:01Z)
Pre-landing three:
- In-seat contract-tier PASS on record: comment 5551770555 on this card (head
649c920) plus the delta PASS at comment 5551906272 covering the docs/pin patch round up to heada415c0c0;Implemented-by:/Reviewed-by:lines carried there. - Both carriers cleared: card side and PR side no longer carry
needs:contract-review(PR feat(types): declare the CEL envelope onvisible/hidden/disabledas one sharedExpressionWire(objectui#7530, ruled A) #7726 labels read back at 13:00Z:package: types,package: react,tests).node scripts/pm/check-clause2-carriers.mjs --pair 7726exits 3 from this seat (REST session-gated, seat post §4) — the three limbs were verified by hand instead. - Check runs on
a415c0c0: 29completed: success+ 3skipped, none failed, none pending;mergeable_state: cleanafter the ready flip.
Landing actions:
draft:falseat 12:59Z.- Auto-merge enabled with
mergeMethod: SQUASHexplicitly at 13:00Z (tool echoes empty method/time — known reading, seat post §4). - Positive probe at 13:01Z:
git ls-remote origin 'refs/heads/gh-readonly-queue/main/*'showsrefs/heads/gh-readonly-queue/main/pr-7726-a00db9ef901195d6e3319e4c82345d44f2fd5e1aatc354ce5d;main=a00db9ef(PR fix(types): lift the flex example's direction/gap out of the inertpropsenvelope #7729's squash). PR fix(types): describeClassNameStylePropsSchemaby its two keys #7730 is queued behind this one.
Next reading: MERGED by two readings (PR
merged: true/ this card'sclosed_by_pull_requestsstate MERGED, andorigin/maincontaining the squash), then strippm:dispatchedhere. Until thenpm:dispatchedstays on.
Generated by Claude Code
- In-seat contract-tier PASS on record: comment 5551770555 on this card (head
MERGED — two readings (2026-09-05T13:19Z, session
session_01BAZFhALsQsGqxui8sNqM8s): (1) PR #7726 readsmerged: true,merged_at13:16:16Z, and this card'sclosed_by_pull_requestsnames PR #7726 in state MERGED (card closed 13:16:18Z,state_reason: completed); (2)origin/maincontains the squashc354ce5d("feat(types): declare the CEL envelope onvisible/hidden/disabledas one sharedExpressionWire(objectui#7530, ruled A) (#7726)"),maintip9587fc95at 13:19Z. Landed heada415c0c0; ruling A executed as recorded in the PASS (5551770555) and delta PASS (5551906272).pm:dispatchedstripped in the same round (the auto-close byFixesleaves it on — seat post §4);domain:specandpriority:p3stay. Follow-up on record: #7725 (the same wire union still spelled inline elsewhere — finding, for triage).
Generated by Claude Code
Filed by the director seat from the #7455 ruling (2026-09-03, decision batch #24): widening
hiddentoboolean | stringcloses the string-form gap on the third key, and the maintainer asked for the remaining gap — the envelope object form — to be decided once for all three keys rather than key by key.The measured fact (from #7455's triage, comment 5524392233,
origin/mainac8d523)hasDeclaredPredicate(packages/core/src/evaluator/declaredPredicate.ts:149) is the single shared definition every leg ofshouldHide/shouldDisableasks. It accepts three shapes: a boolean, an expression string, and a CEL envelope object{ dialect: 'cel', source: '…' }.hidden:SchemaRenderer.hiddenDeclaredGate.test.tsx:188exerciseshidden: { dialect: 'cel', source: 'true' }through aRecordcast.visible/disabledareboolean | string(base.ts:281/:354,base.zod.ts:158/:190),hiddenbecomes the same under finding(types,docs):BaseSchema.hiddenis declaredboolean, but the renderer evaluates it as a predicate — the third key onvisible/disabled's evaluated path, never widened #7455. The zod mirror atbase.zod.tsisz.string()for the expression half — it does not accept the envelope. The envelope schema exists only onFormFieldSchema(form.zod.ts:583,ExpressionWireSchema).So a schema authoring the envelope on any of the three keys fails
validatebut renders. Declared ≠ enforced, in the direction that gives an AI author no signal.Options
boolean | string | ExpressionWire; zod: union with the existing envelope schema; docs rows updated; the existing pins stop casting. Grows the published surface of three keys by one shape that already ships.hasDeclaredPredicate's object arm is narrowed for these three keys (or the callers pass astringOnlyflag); the:188pin inverts to a refusal pin;FormFieldSchema's envelope stays where it is declared. Declaration unchanged, runtime narrowed to it.Not in scope
The string form of
hidden(#7455, ruled A). ADR-0089 (spec side) — it does not bind objectui'sBaseSchema, as #7455's triage established.Refs #7455 · #4581 · #4580 Q3-A · #7088.