Skip to content

finding: two authored record:alert page-block visible predicates read record.* unguarded — same sparse-face fault class as #8990, but outside its action-only census #9167

Description

@os-zhuang

Filed unassigned by the domain:metadata seat while implementing #8990 (session session_01NTKPDRoynY8i3HmdSFUxFj, PR #9166). Recording only — not claimed, not queued. Backlink: #8990, #8975.

What the #8990 census did not cover

#8990's census counts actionvisible / disabled predicates: 34 of them, all now accounted for. Re-measuring that census on origin/main @ 3851f87f0 turned up two more authored predicates in the same fault class that are not actions, so no #8990 assertion reaches them and neither is migrated by PR #9166:

  • packages/platform-objects/src/pages/sys-user.page.ts:79
    visible: 'record.id == ctx.user.id && record.email_verified == false'
  • examples/app-showcase/src/ui/pages/task-detail.page.ts:51
    visible: "record.status == 'in_review'"

Both are record:alert page-block predicates, both read record.* with no has() guard, and both bind the record the client fetched rather than a total one.

Why this is an observation and not a defect claim

The severity depends on a fact this seat did not measure: what a page-block predicate actually binds, and what the renderer does when one faults. The two differ from the action face in the ways that matter:

  1. A record:alert block sits on a record-detail page, so its binding is a detail read rather than a list row. If a detail read always projects every declared column, the absent-key half of the fault class never fires here and only the projected-null half is live — and both predicates above are bare equalities, which CEL answers rather than faults on a null.
  2. The failure DIRECTION is unknown here and is the opposite of the action face on at least one renderer. The action face is fail-closed (the button vanishes). objectui#4807 records the record:alert surface as fail-soft — "fail-soft 使作者门控的横幅永远显示", i.e. an unevaluable predicate leaves the banner permanently VISIBLE. A fail-soft record:alert on sys_user would show every user "Email not verified" including verified ones, which is a louder and differently-shaped bug than a missing button.

So the right first step is a measurement, not a rewrite: boot the real app, open a record-detail page whose read does not project the gated column, and see what the banner does. That is a dogfood-verification job.

Why it is filed rather than folded into #8990

#8990's card, its census, and its dispatched file surface are all scoped to action predicates, and PR #9166 stayed inside that scope deliberately. Migrating a page-block predicate changes what a banner does on a live surface with an unmeasured failure direction — exactly the kind of thing #8990 refuses to do blind for the showcase specimen. Extending #8990's scope after the fact would also silently invalidate its "34" figure, which several documents now cite.

What is owed, if it is taken up

  1. Measure the page-block predicate binding and the renderer's fault direction (fail-open vs fail-closed), on both the platform sys_user page and the showcase task-detail page.
  2. If the binding can be sparse, guard both predicates per the rule on materializeDeclaredFields in @objectstack/objectqlhas() alone for these two, since both are bare equalities against literals.
  3. If the direction is fail-SOFT, the guard is not merely cosmetic there: has() turning a fault into false changes a permanently-shown banner into a correctly-hidden one, which is a user-visible fix and wants its own before/after.

No lint rule: #8881 / PR #8979 declined the mirror gate on measured grounds and that verdict covers this surface too — sparseness is a property of the view's projection and of row data, not of the metadata a linter sees.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions