Skip to content

spec: a page visibleWhen over the CEL engine's maxAstNodes (256) passes PageSchema at publish, then faults on every evaluation in the console and fails open, so the page gate it declares is never enforced #24953

Closed

Description

@objectstack-fleet

Filing gate ①: class (c), declared ≠ enforced, measured. Filed by repo:cloud#1 (seat post #6026, session session_01FSg7JRUiTPo8GrgT38tAWP) from the objectstack-ai/cloud#3108 dev report (cloud comment 6297789614). ⛔ Not a claim.

Reader who acts: triage grades and routes it (expected domain:spec, with @objectstack/formula bounds and the page schema's predicate fields).

Dedupe: objectstack issues of both states (444 read) grepped for maxAstNodes, ast node, predicate size, bounds and fail open: 0 hits on point.

What happens (framework 9d13b5fc, objectui 616df659)

  • packages/formula/src/cel-engine.ts (objectstack main 74fcec1e, :532-534) states the platform contract: 「A source over maxAstNodes / maxDepth / maxListElements does not parse here, because it does not parse anywhere else on the platform either」. DEFAULT_LIMITS.maxAstNodes is 256.
  • A cloud page declared a page:tabs item visibleWhen of 391 AST nodes (cloud commit cf2d31b7).
    • It passed PageSchema parse and the authoring-time gates: cloud's check:action-predicate-roots, 654 sources, green.
    • On a local stack with the pinned console, every evaluation faulted with [bounds] Exceeded maxAstNodes (256) for that item.
    • The renderer failed open, so the tab drew for a user the predicate meant to exclude.
  • Cloud worked around it in its own PR (an exactly equivalent shorter composition, objectstack-ai/cloud PR ObjectLogger file destination silently no-ops in ESM builds #3110). The platform gap stays.

Wanted

  • A predicate field the page schema declares (visibleWhen and its siblings) is refused at publish with the same bounds verdict validateExpression gives. That is a loud VALIDATION_ERROR naming the field and the bound, the "declare = enforce" rule, instead of a runtime fault that fails open.
  • A test: a page whose visibleWhen exceeds maxAstNodes is refused at parse or publish.

Acceptance

  • The 391-node source above is refused when the page is registered or published, with the bound named.
  • A source within bounds is unchanged.

Activity

  1. added
    bugSomething isn't working
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    and removed on Oct 20, 2026
  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 21
    Session: session_01D6F9axfoPzR7t8G1J7Ytzz
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-24953-page-predicate-bounds
    Worktree: objectstack-issue-24953
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main `` or later; stop on a breach and explain it in the report):

    • the page schema's predicate fields (packages/spec/src/ui/page.zod.ts and the page:tabs item / component visibleWhen slots it declares), or the packages/lint expression pass that judges them;
    • the bounds verdict reused from @objectstack/formula (validateExpression / DEFAULT_LIMITS), not re-implemented;
    • tests, generated artifacts and .changeset/24953-*.md.
      Container & model: M, mode:subagent, model: default (no path-derived mandate)
      Clause-②: no
      Responsibility: n/a — a declared predicate that fails open at runtime (declared ≠ enforced)
      Thread-read: none
      Serial constraints cleared: read at this act; the dev re-checks open PRs touching page.zod.ts and the expression pass before opening its own.

    Under 「甲」 (#6015 6292005854): refusing an over-bounds predicate that passes today is a narrowing no conversion can carry. So 18.x ships it as a WARNING at os validate / os build / the publish door, and the refusal is filed for 19.0, unless the dev measures otherwise and says so. Clause-② stays no either way.

    Read on origin/main `` at 2026-10-20T20:24Z: packages/formula/src/cel-engine.ts declares that a source over `maxAstNodes` (256) "does not parse anywhere else on the platform either". `PageSchema` accepts a 391-node `page:tabs` item `visibleWhen` (objectstack-ai/cloud#3108).


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim correction · domain:spec seat 3 · 2026-10-20T20:25Z · ⛔ Not a claim.

    Claim 6300692037 prints a blank origin/main sha, because a git fetch failed with HTTP 502 at the act. The read and the worktree are on origin/main f32d795ce5.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24953,
      "status": "done",
      "branch": "claude/issue-24953-page-predicate-bounds",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24999",
      "session": "session_01D6F9axfoPzR7t8G1J7Ytzz",
      "premise_still_valid": true,
      "summary": "Premise reproduced on origin/main f32d795ce5. A page:tabs item visibleWhen of 98 || terms (489 AST nodes; validateExpression answers cel-too-large, maxAstNodes, 256) passed PageSchema.safeParse and drew 0 findings at runAuthoringRules validate and build, and 0 errors and 0 advisories at the page publish door. The same source on the component NODE visibleWhen was already an ERROR (visibility-predicate-over-budget, at every door). So the bound is already enforced on the node slot, and the visibility pass misses every CEL slot inside the properties bag. Measured clean on main: object-form section visibleWhen, object-form inline field visibleWhen, record:alert visible. The new rule page-predicate-over-budget (packages/lint/src/validate-page-predicate-bounds.ts) parses each walked component's properties with its ComponentPropsMap row and judges every position where the parse produces a cel envelope, so it keeps no key list. The verdict is validateExpression's own cel-too-large, with its limit and limitValue. The finding names the key and the bound and does not echo the source. It does not descend composition slots, so nothing is reported twice. Per 甲 it is a WARNING (advisory tier) at os validate, os build, os lint and the page door advisories channel. A within-bounds source is unchanged. Four content/docs CLI transcripts move the author-time rule count from 49 to 50 (check:docs-transcript-drift). Changeset: @objectstack/lint minor, Clause-② no. PR body says Closes #24953. The 19.0 refusal text is in followup_19_0 for the seat to file.",
      "tests": "Head cceddfcb61. Repro before the fix: a temporary probe file on base f32d795ce5, since deleted. It printed 'PageSchema ok? true', 'validate []', 'build []', 'door errors [] adv []', and the node-level control 'component [[visibility-predicate-over-budget,error,pages[0].regions[0].components[0]]]'. New suite: pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-page-predicate-bounds.test.ts gave 18 passed (VERDICT command-exit 0). Full lint suite: 152 files passed, 1 failed (the pinned page-door rule roster in runtime-gate.page-visibility-predicate-writes.test.ts). The roster was updated, and that file then passed 15/15. pnpm --filter @objectstack/lint typecheck gave VERDICT command-exit 0 (tsc --noEmit plus check:test-typecheck OK). Gates: dispatch-gates --commands derived 65 commands, and 29 more after the docs edit. Exit codes were captured with redirect-then-exit. Every command exited 0 except three. check:docs-transcript-drift went red (stale count 49), was fixed, and reran green ('4 declared transcript value(s) across 418 page(s) ... equal'). check:dual-build-cjs-loads gave exit 3, PREREQUISITE NOT MET (no full-repo dist): NOT MEASURED, declared to CI. check:lean-entry-closure gave exit 3, then MODULE_NOT_FOUND on metadata-core dist after a partial objectql build: NOT MEASURED, declared to CI. @objectstack/spec check:skill-examples gave exit 3, PREREQUISITE NOT MET (client dist): NOT MEASURED. pnpm --filter @objectstack/spec check:generated exited 0 (spec untouched). dispatch-gates --ran gave '94 derived famil(ies) accounted for'. No ablation was run: the red-before-green is the repro above, plus the node-level control and the within-bounds control in the suite. packages/cli integration tier: not touched, declared to CI.",
      "mcp_calls": "0",
      "api_writes": "2 — fleet-write relay (objectstack-fleet[bot]): pr_create (POST /repos/objectstack-ai/objectstack/pulls draft, plus POST /issues/24999/assignees baozhoutao) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/24953/comments). Plus git push (not REST). Zero label writes: the dispatch named no label and the diff carries a changeset.",
      "open_questions": [],
      "followup_19_0": "Title: lint: page-predicate-over-budget becomes an error at 19.0 (refuse a page component props CEL slot over the platform parse bounds). Body: In 18.x, page-predicate-over-budget (validatePagePredicateBounds, @objectstack/lint, landed by #24999 for #24953) reports a warning at os validate, os build, os lint and the page publish door. It fires on a CEL slot declared inside a page component's properties bag, such as a page:tabs item visibleWhen, object-form section and inline field visibleWhen / readonlyWhen / requiredWhen, record:alert visible, or action:* visible / disabled, whose source overruns DEFAULT_LIMITS (maxAstNodes 256, maxDepth, maxListElements). Such a source does not parse anywhere on the platform. It faults on every evaluation, and the console fails open. It is a warning only because of 甲 (#6015, record 6292005854): the refusal is a narrowing that no ADR-0087 conversion can carry. Wanted at 19.0: (1) the finding's severity becomes error, and the authoring-rules entry moves its tier from advisory to gating in the same edit, so a page carrying such a slot fails os build and is refused at the page door with 422 VALIDATION_ERROR on its next save. Stored pages keep loading, because the gate judges writes only. (2) The message drops 'A future major release refuses it.' (3) validate-page-predicate-bounds.test.ts flips its warning assertions to error and adds a door-refusal pin with code and status. (4) A changeset marks it BREAKING with FROM (warning, page saves) and TO (error, page refused), plus the one-line fix: shrink the predicate, or precompute it into a formula or roll-up field. Acceptance: the card's ~390-node tab-item visibleWhen is refused at os build and at the page door with the bound named. A within-bounds source is unchanged. Dedupe words: page-predicate-over-budget, maxAstNodes, page:tabs visibleWhen, 19.0 refusal.",
      "out_of_scope_findings": [
        "class: a (fails open, user-visible) · reach: objectui console renders a page:tabs item whose visibleWhen faults ([bounds] Exceeded maxAstNodes (256), the card's cloud repro): the tab draws for a user the predicate excludes · repo: objectui (the tab-item evaluator's fault fallback is visible=true) · dedupe words: tabs visibleWhen fail open, predicate fault fallback, evalFieldPredicate fallback",
        "class: c · reach: os validate on base f32d795ce5 — a page:tabs item visibleWhen 'record.x === 1' (not CEL) parses through PageSchema and draws 0 findings at validate/build/page door; same for object-form section/field predicates and action:* visible inside the props bag (only bounds are judged by the new rule; the node-level visibility family's syntax/unknown-function verdicts are not borrowed because a tab item's environment differs: data = row, bare fields spread) · evidence: repro probe 'tab item syntax fault parse true []' · dedupe words: props-bag predicate syntax, tab item visibleWhen syntax, page:tabs predicate not judged",
        "carrier: PR #24947 (authoring-rules.ts, different entry) · noted, not filed"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:spec seat 3 · baozhoutao · session session_01D6F9axfoPzR7t8G1J7Ytzz · 2026-10-21T00:07Z

    Noted, not filed: props-bag predicates get no syntax or unknown-function verdict, only bounds. The node-level family is not borrowed because a tab item's evaluation environment differs.

    Claim 6300692037 ends here; pm:dispatched is removed in this stroke.


    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

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions