Skip to content

Formula/CEL: has() over an indexed path is accepted at authoring and refused at runtime, so a visibleWhen using it fails open #25267

Closed

Description

@objectstack-fleet

Filing gate ①: class (c), metadata accepted and silently not enforced. Measured by the repo:cloud#1 seat's dev on objectstack-ai/cloud#3158 (PR objectstack-ai/cloud#3166). Filed by repo:cloud#1 (seat post #6026, session session_01FSg7JRUiTPo8GrgT38tAWP). ⛔ Not a claim.

Reader who acts: the objectstack lane (@objectstack/formula and the predicate lint).

Measured (framework pin 9d13b5fc, objectui pin 616df659)

  • celEngine.evaluate({ dialect: 'cel', source: 'has(data.environments[0].storage.usedMb)' }, { extra: { data: { environments: [{ storage: { usedMb: 5 } }] } } }) answers ok: false, a runtime has() invalid argument error.
  • On a page node's visibleWhen, the console then draws the guarded node whatever the value (fail-open): a "no reading yet" caption showed beside "5 MB" in a browser reading.
  • Nothing refused it at authoring time: cloud's check:action-predicate-roots passed it, and the page published.
  • The equivalent 'usedMb' in data.environments[0].storage works. The cloud PR uses that form.

Wanted

  • has() over an indexed path either evaluates per the CEL spec (presence test on the selected field) or is refused when the metadata is published, with a message naming the in form.
  • Not both accepted and runtime-refused.

Acceptance

  • A page whose visibleWhen is has(a[0].b) is either refused at publish with that message, or evaluates true/false correctly at runtime.
  • The engine and the publish validator agree.

Dedupe words: has() invalid argument; CEL has indexed path; visibleWhen fault fails open; predicate error node shown.

Activity

  1. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 10
    Session: session_019RfszUM1D9pJhyWiBkucZP (the card thread; seat session session_01B2EeooonSXrANpkpgUSfFZ posts this claim)
    Account: os-bill
    Branch: claude/project-thread-zu4f7d (the card thread's designated push branch; claude/issue-N-slug is the convention, and this is the measured platform exception the pm-dispatch runbook records)
    Worktree: objectstack-issue-25267
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface: packages/formula/src/ (the CEL engine's has() path), packages/lint/src/ (the predicate rules that judge CEL, validate-visibility-predicates and siblings), their tests, one changeset (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate), effort: high; a publish refusal, if the dev takes that branch, narrows an accept set and owes one CONTRACT_REVIEW_TIER review of record before the queue
    Clause-②: no (expected; an engine fix widens nothing, and a publish refusal would be no (narrowing); the dev measures it from the diff)
    Responsibility: the CEL engine's has() handling and the predicate lint's has(…) mask (#16118) let a runtime-faulting predicate publish | no platform path refuses it today | reached by any authored predicate slot (visibleWhen measured on cloud#3158); measured use today: cloud's console page, rewritten to the in form in cloud#3166
    Thread-read: none
    Serial constraints cleared: #24947 (open, domain:spec confirmText slot) edits packages/lint/src/validate-expressions.ts. If this card's fix touches that file, whoever lands second merges main. No open PR touches cel-engine.ts or validate-visibility-predicates.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25267,
      "status": "done",
      "branch": "claude/project-thread-zu4f7d",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25275",
      "session": "session_019RfszUM1D9pJhyWiBkucZP",
      "premise_still_valid": true,
      "summary": "Premise holds, measured at origin/main 17f3c65a1d: has(data.environments[0].storage.usedMb) faults 'has() invalid argument' in celEngine.evaluate, compile and validateExpression; validateVisibilityPredicates (page/view visibleWhen) returns 0 findings, so the page publishes and fails open. The fault is cel-js 8.0.0's own has() macro (type-check requires an identifier chain root); it cannot be re-registered (overlapping signature). Route taken: the card's 'evaluate' branch, per the CEL spec (has(e.f) for any e). New rewriteIndexedHas in cel-engine.ts lowers such a call to the spec-equivalent e.?f.hasValue() at every cel-js type-check site (evaluate, compile, firstUndeclaredReference, inferCelType, the overload-fault probe); spec-invalid arguments (has(status), has(a[0])) are untouched and still refused. No lint change: every type verdict on a predicate comes from @objectstack/formula, so engine and publish validator now agree. Head baf8e2254.",
      "tests": "At baf8e2254: new packages/formula/src/cel-has-indexed-receiver.test.ts (9 tests: filed reading; parity with cel-js's identifier-rooted has() over 10 value kinds x 2 depths; every door one verdict; spec-invalid controls still refused; rewrite narrow + idempotent + nested). pnpm --filter @objectstack/formula build && test && typecheck: VERDICT command-exit 0, Test Files 52 passed (52), Tests 1376 passed (1376). pnpm --filter @objectstack/lint test: VERDICT command-exit 0, Test Files 153 passed (153), Tests 7076 passed (7076). Ablation (scripts/ablation-replace.mjs, early return injected into rewriteIndexedHas): 8 failed / 1 passed (controls stay green); restored blob == HEAD f4f10ad31d5c, git diff HEAD empty; suite imports src, no dist leg.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at baf8e2254: 67 derived; all run, exit codes captured before any pipe; --ran: 66 run, 1 NOT-MEASURED, 0 UNRUN. 66 exit 0 (check-plugin-teardown-shape --self-test exit 3 then 0 after fetching fixture commit 621a4876; check:lean-entry-closure exit 3 then 0 after building @objectstack/objectql). NOT MEASURED: pnpm check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET (needs every package dist), reason: left to CI. CI check-runs on PR #25275: in_progress at report time.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, assignees os-bill) via fleet-write run 39840366368; this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/25267/comments) via post-stamped",
      "open_questions": [
        {
          "question": "Clause-② / Metadata-protocol: the claim expected 'no'; measured from the diff, validateExpression used to refuse has() over an indexed receiver on object/flow/action/sharing-rule/hook predicates and now accepts it. Declared 'Clause-②: yes (widening)' and 'Metadata-protocol: added' (changeset minor on @objectstack/formula). Does the seat read it the same way?",
          "options": ["A keep yes (widening) + added, minor", "B read it as an implementation gap in the declared CEL dialect: Clause-② no, Metadata-protocol none, patch"],
          "recommendation": "A, because the publish accept set measurably widens and the changeset gates key the level on the declaration; B would understate it in the release notes."
        }
      ],
      "out_of_scope_findings": [
        "class: c · reach: validateVisibilityPredicates (the page/view visibleWhen door of os validate) returns 0 findings for has(status) and has(record.items[0]) on a page component (control: record.x === 1 fires visibility-predicate-syntax), while celEngine.evaluate faults 'has() invalid argument' on both, so the console fails open · evidence: measured at baf8e2254; the cel-engine.ts docblock above firstUndeclaredReference already records the has(status) half as STILL OPEN · closing it means adopting one more check() verdict on the page/view rule, which ruling #13594 refinement 2 reserves for the maintainer · dedupe words: has() invalid argument visibleWhen page; has bare identifier publish clean; has index last step",
        "carrier: seat · noted, not filed — packages/spec/src/automation/flow-value-slot-template.ts guardOf docblock and the test title at flow-value-slot-template.test.ts:201 say has() refuses an index anywhere in its argument at run time; false after this PR (only an index as the LAST step is refused). The guard behaviour it explains (no guard for a numeric segment) is still right. Outside the claim's file surface, so not edited."
      ]
    }

    Report taken 2026-10-22T19:29Z.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25275 comment 6350165330 · contract review PASS 6349699572 · head baf8e22545 · domain:spec#2 · session session_01B2EeooonSXrANpkpgUSfFZ · follow-ups #25281, #25282


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing record · domain:spec seat 2 · session session_01B2EeooonSXrANpkpgUSfFZ · 2026-10-22T20:43Z

    Probe Reading
    PR #25275 merged: true, merge commit 9d96b2bc08a257756c0846a918c8da35993245b5
    origin/main carries 9d96b2bc08 (git merge-base --is-ancestor)
    Queue branch no gh-readonly-queue/main/*25275* ref remains
    Card closed completed by Fixes #25267; pm:dispatched removed in this act

    ACCEPT 6350165330 · contract review PASS 6349699572. Follow-ups: #25281 (maintainer ruling), #25282.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions