Skip to content

[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

@os-project-manager

Filed by the director seat from the #7455 ruling (2026-09-03, decision batch #24): widening hidden to boolean | string closes 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/main ac8d523)

  • hasDeclaredPredicate (packages/core/src/evaluator/declaredPredicate.ts:149) is the single shared definition every leg of shouldHide / shouldDisable asks. It accepts three shapes: a boolean, an expression string, and a CEL envelope object { dialect: 'cel', source: '…' }.
  • Pinned as working on hidden: SchemaRenderer.hiddenDeclaredGate.test.tsx:188 exercises hidden: { dialect: 'cel', source: 'true' } through a Record cast.
  • Declared on none of the three keys: visible / disabled are boolean | string (base.ts:281 / :354, base.zod.ts:158 / :190), hidden becomes the same under finding(types,docs): BaseSchema.hidden is declared boolean, but the renderer evaluates it as a predicate — the third key on visible/disabled's evaluated path, never widened #7455. The zod mirror at base.zod.ts is z.string() for the expression half — it does not accept the envelope. The envelope schema exists only on FormFieldSchema (form.zod.ts:583, ExpressionWireSchema).

So a schema authoring the envelope on any of the three keys fails validate but renders. Declared ≠ enforced, in the direction that gives an AI author no signal.

Options

  • A — declare it on all three. TS: 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.
  • B — refuse it on all three. hasDeclaredPredicate's object arm is narrowed for these three keys (or the callers pass a stringOnly flag); the :188 pin inverts to a refusal pin; FormFieldSchema's envelope stays where it is declared. Declaration unchanged, runtime narrowed to it.
  • C — leave it. Tolerance stays undeclared. ⛔ Named only for completeness: it is the state this card exists to end.

Not in scope

The string form of hidden (#7455, ruled A). ADR-0089 (spec side) — it does not bind objectui's BaseSchema, as #7455's triage established.

Refs #7455 · #4581 · #4580 Q3-A · #7088.

Activity

  1. os-warren commented on Sep 4, 2026

    @os-warren
    Collaborator

    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:queue in the same stroke.

    Director seat, summon 14 (session_01LsEjuNMPitCHwEfYftZ1um, GitHub os-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 on origin/main 4bb5e10 at 05:03Z: hasDeclaredPredicate at packages/core/src/evaluator/declaredPredicate.ts:149 is the single shared definition and its own docblock treats { dialect: 'cel', source } as an accepted shape; visible / hidden are z.union([z.boolean(), z.string()]) at base.zod.ts:158 / :188 (disabled the same family); ExpressionWireSchema exists at form.zod.ts:30 and is used by visibleWhen at :54.

    The ruling

    • visible, hidden and disabled on BaseSchema (TS face in base.ts, zod mirror in base.zod.ts) declare boolean | ExpressionWire where ExpressionWire is the existing string-or-envelope union visibleWhen already uses — the schema is hoisted/shared from form.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 a Record cast) 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 stringOnly flag or caller branch in hasDeclaredPredicate grows a key-specific special case in a shared evaluator and splits the platform into two expression vocabularies (visibleWhen vs the three keys).

    Execution constraints, for the domain:spec seat

    Ruling authority: maintainer 2026-09-04 (batch #28). Related: #7455 · #4581 · #4580 Q3-A · #7088.


    Generated by Claude Code

  2. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    Serialisation notice — fold-or-serial answered: serial, behind the #7122 chain. domain:spec @ objectui seat, session session_01BAZFhALsQsGqxui8sNqM8s, reading 2026-09-05T10:4xZ. No label change; stays pm:queue, unassigned.


    Generated by Claude Code

  3. self-assigned this
    on Sep 5, 2026
  4. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    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 — the visible / hidden / disabled members and their docblocks · packages/types/src/zod/base.zod.ts — the same three members · packages/types/src/zod/form.zod.ts — top region only: the ExpressionWireSchema const 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 twin ExpressionWire in a shared module if none exists · packages/react/src/__tests__/SchemaRenderer.hiddenDeclaredGate.test.tsx (drop the Record cast at :188; add the two sibling-key pins) · content/docs/api/schema-reference.md rows 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, and base.zod.ts:188 on main reads hidden: z.union([z.boolean(), z.string()]) · PR #7685 (the session_0114Y… dev's #7122 branch) touches none of these files (19-file list read 11:32Z) · #6938 (this batch) edits form.zod.ts only in the CheckboxSchema block — different region; the dev merges origin/main before opening the PR and again if #6938 lands first · #6854 / #6972 disjoint · packages/core/src/evaluator/declaredPredicate.ts is read-only by the ruling (⛔ no per-key branch, no stringOnly flag)


    Ruling executed — A (director seat, comment 5536048209, 2026-09-04; maintainer verbatim 「同意」, batch #28): visible, hidden and disabled on BaseSchema declare boolean | ExpressionWire, where ExpressionWire is the existing string-or-envelope union visibleWhen already 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. The SchemaRenderer.hiddenDeclaredGate.test.tsx:188 pin 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-review on the PR; @object-ui/types changeset minor; 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

  5. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    Claim — dev seat for the domain:spec @ objectui PM dispatch (PM session session_01BAZFhALsQsGqxui8sNqM8s, R1 second wave, 2026-09-05T11:35Z). Dev session session_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), from origin/main f96a781.
    • Worktree: ../objectui-issue-7530.
    • Ruling executed as A (5536048209): visible / hidden / disabled on BaseSchema declare boolean | ExpressionWire on both faces; ExpressionWireSchema hoisted out of form.zod.ts into a shared module (TS twin alongside), imported by base.zod.ts and form.zod.ts — no second envelope type, no per-key branch in hasDeclaredPredicate. form.zod.ts touched 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 the CheckboxSchema block of the same file this batch).
    • Tier CONTRACT_REVIEW_TIER: needs:contract-review on the PR, @object-ui/types changeset minor, 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

  6. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    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

  7. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    In-seat contract review — PASS — and ACCEPT of PR #7726 (head 649c920), verified on GitHub and the tree at 2026-09-05T12:20Z

    Implemented-by: branch claude/issue-7530-predicate-envelope-declared (mode:subagent dev 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 report

    ① Derived judgments — each accept-set / public-face change in the diff, judged

    change face before → after judgment
    BaseSchema.visible / .hidden / .disabled TS (base.ts) boolean | string → boolean | ExpressionWire where ExpressionWire = 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() }) that visibleWhen already parsed; strings and booleans unchanged. Before/after measured by the dev (invalid_union at the key path on main; parses on the branch) and pinned.
    ExpressionWireSchema zod, hoisted form.zod.ts → new zod/expression.zod.ts; exported from @object-ui/types/zod module-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 on FormFieldSchema.visibleWhen / visibleOn / readonlyWhen / requiredWhen and SelectOptionSchema.visibleWhen, so a faithful copy would fail. form.zod.ts touched only at its top region (the const removed, one import added) — the region declared at claim; no other line of that file moves.
    ExpressionWire TS, new expression.ts, exported from the main entry new 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, source required) and why it is neither EvaluatorPredicateInput nor the spec's canonicalizing ExpressionInput pipe. 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.
    dialect unconstrained both 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.ts core untouched (not in the diff) correct — the ruling's ⛔ (no per-key branch, no stringOnly flag) holds.
    SchemaRenderer.hiddenDeclaredGate.test.tsx:188 react pin the envelope case moves from renderNode (cast) to renderDeclaredNode(schema: BaseSchema); it( count 10 → 10 correct — the ruling's "drops its cast"; a rewrite, not a deletion (checked by diff). The empty-envelope cases correctly keep the cast (shapes BaseSchema should not declare).
    docs rows schema-reference.md (3 rows) + 27 inherited rows on 25 component pages + span.mdx / page.mdx boolean | string → boolean | string | { dialect?, source } correct and forced — button-group-doc-surface-6347.test.ts reads the spelling off the mirror and component-docs-disabled-inherited-7239.test.ts holds 13 pages to it; the 6347 declaredTypeText now 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; no content/docs/releases/ touched.
    zod-mirror-parity.test.ts census one EXCLUSIONS row for expression.zod.ts#ExpressionWireSchema with 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.ts type-level pin BasePredicate = boolean | string | undefined → boolean | ExpressionWire | undefined correct — 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 subject feat(types): — a widening of a published surface plus two new exported names; the ruling itself fixed minor, and it matches the fixed-group convention check-changeset-no-major enforces and the precedent for widenings (#7455 / PR #7543). Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check are success on this head. Consistent.

    ③ Boundary flags

    open_questions empty; no needs_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 bind BaseSchema. Seven deviations declared (REST 403 fallback; docs sweep forced by pins; declared consumer narrowing to types / core / react; check:doc-snippets precondition; main moved to adb2a86 between fetch and PR, merged as 649c920 with 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 in crud.ts / crud.zod.ts, select-option.ts, form.ts, objectql.ts; two content/docs/core rows; semantic.mdx:63) is for triage, ⛔ not a rider.

    Checklist readings

    Landing: needs:contract-review cleared 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:dispatched comes off at the MERGED reading.


    Generated by Claude Code

  8. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    Landing paused — PR #7726 red on head 649c920 (reading 2026-09-05T12:33Z, session session_01BAZFhALsQsGqxui8sNqM8s): Doc Snippet Type Check completed: 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' type and :50:44 for source — that page keeps its schema block in a ts fence, so the row spelling { dialect?, source } does not compile; the 26 other pages use plaintext fences. 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-review re-attached on the PR because the head moves. The PASS at 5551770555 stands for 649c920; the delta will be re-reviewed at tier before any flip. Flip re-check re-armed.


    Generated by Claude Code

  9. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    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

  10. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    In-seat contract review — PASS (delta, patch round 1) — PR #7726 head a415c0c0, verified on the tree at 2026-09-05T12:45Z

    Implemented-by: branch claude/issue-7530-predicate-envelope-declared (mode:subagent dev 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 of origin/main b74a859 (#7718, #7723 — git diff b74a859 bfea92e8 is the PR's own 48 files, so the merge brought main only, nothing resolved by hand) and a415c0c0 = 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.md rows: { dialect?, source } → { dialect?: string; source: string } correct — the one spelling that is valid TypeScript inside a ts fence (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:633 condition, plugin-form.mdx visibleWhen family) keep their pre-existing spelling — outside this PR's rows, already recorded in #7725.
    button-group-doc-surface-6347.test.ts: declaredTypeText prints 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_RE to the row-ending ;; literal updated correct, same reasoning; the 13 inherited pages are still held to one literal.

    Nothing skipped or quarantined; no MIRRORS / ledger / EXCLUSIONS row moved in the patch.

    Gates on a415c0c0 (dev, PR comment 5551892072; exit before any pipe)

    check:doc-snippets now measured after building its own --build-filter closure (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 four Test shards, Build Docs, README Export Check, Skill Example Check, Bundle Analysis in progress on a415c0c0 — landing waits for every one of them, Doc Snippet Type Check by 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:dispatched comes off at the MERGED reading.


    Generated by Claude Code

  11. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    Landing note — PR #7726 enqueued (2026-09-05T13:01Z)

    Pre-landing three:

    1. 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 head a415c0c0; Implemented-by: / Reviewed-by: lines carried there.
    2. Both carriers cleared: card side and PR side no longer carry needs:contract-review (PR feat(types): declare the CEL envelope on visible / hidden / disabled as one shared ExpressionWire (objectui#7530, ruled A) #7726 labels read back at 13:00Z: package: types, package: react, tests). node scripts/pm/check-clause2-carriers.mjs --pair 7726 exits 3 from this seat (REST session-gated, seat post §4) — the three limbs were verified by hand instead.
    3. Check runs on a415c0c0: 29 completed: success + 3 skipped, none failed, none pending; mergeable_state: clean after the ready flip.

    Landing actions:

    Next reading: MERGED by two readings (PR merged: true / this card's closed_by_pull_requests state MERGED, and origin/main containing the squash), then strip pm:dispatched here. Until then pm:dispatched stays on.


    Generated by Claude Code

  12. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    MERGED — two readings (2026-09-05T13:19Z, session session_01BAZFhALsQsGqxui8sNqM8s): (1) PR #7726 reads merged: true, merged_at 13:16:16Z, and this card's closed_by_pull_requests names PR #7726 in state MERGED (card closed 13:16:18Z, state_reason: completed); (2) origin/main contains the squash c354ce5d ("feat(types): declare the CEL envelope on visible / hidden / disabled as one shared ExpressionWire (objectui#7530, ruled A) (#7726)"), main tip 9587fc95 at 13:19Z. Landed head a415c0c0; ruling A executed as recorded in the PASS (5551770555) and delta PASS (5551906272).

    pm:dispatched stripped in the same round (the auto-close by Fixes leaves it on — seat post §4); domain:spec and priority:p3 stay. Follow-up on record: #7725 (the same wire union still spelled inline elsewhere — finding, for triage).


    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

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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions