Filed unassigned by the domain:devx seat while implementing #11034 (session session_015ahemw8RcTgqtxrj15PEZx). Triage on #11034 ruled this gap is its own gate card and explicitly scoped it out of that PR:
(3) the os:check gap (the snippet fence validates TS shape but not the CEL string) is its own gate card — file it as a finding, don't build it here.
Recording only. Not claimed, not queued, not graded.
The gap
{/* os:check */} blocks in content/docs/** are type-checked by packages/spec/scripts/check-skill-examples.ts (tsc --noEmit over the extracted blocks). A visibility predicate is authored as a bare assignment whose value is typed string:
visibleWhen: "record.status != 'closed' && user.hasRole('admin')"hasRole is not a CEL function — it appears in no stdlib registry and on no contract (see below). The line nevertheless type-checks perfectly, because every CEL string is the same type as every other CEL string. That is what let #11034's example ship and survive: a shipped doc taught a visibility gate whose copy faults at runtime and — because visibility is fail-open — shows the element to everyone.
Why the sibling gate does not already cover it
packages/lint/scripts/check-doc-formula-expressions.mjs closes exactly this class for field formulas, and its own header states the reasoning this card is a second instance of:
check:skill-examples runs tsc --noEmit over blocks marked os:check. Between them, a formula example that compiles perfectly and is semantically wrong passes every gate.
It cannot reach visibleWhen as written, deliberately. Its opt-in is structural and narrow — (A) a Field.<anything>({ … expression … }) factory call, or (B) an object literal carrying both type: 'formula' and expression. A visibleWhen: assignment matches neither, and the gate's header is explicit that predicates are out of its scope because their scope depends on enclosing structure a fragment does not carry.
The hard part, named rather than hand-waved
A visibleWhen gate cannot use one scope for every site, because the binding root really does differ by layer. Measured on origin/main at 365e334 while fixing #11034:
| Site | Binds |
|---|
Page component / app-nav visible | record, current_user, user, ctx.user, os.user, app, features, page.* (objectui app-shell/src/providers/ExpressionProvider.tsx:59,70) |
Per-option visibleWhen | record + the host predicate scope (objectui core/src/evaluator/optionRules.ts:103) |
Form section / form field visibleWhen | record + previousonly — all three resolveFieldRuleState call sites in objectui components/src/renderers/form/form.tsx (1201, 1237, 1935) pass undefined for the scope parameter |
So the same predicate text is correct on one layer and an unbound root on another, and a fragment in a doc fence does not always say which layer it illustrates. A gate keyed on the key alone would produce false reds — the failure mode check-doc-formula-expressions' header calls out as worse than no gate. Any design here has to answer "which layer is this fragment about" first.
Suggested shape (not a decision)
Extend check-doc-formula-expressions' opt-in rather than minting a second opinion about one contract (Prime Directive #12): a visibleWhen / readonlyWhen / requiredWhen slot whose enclosing structure identifies the layer, validated through @objectstack/formula's validateExpression with that layer's scope. Sites whose layer cannot be determined statically are skipped, loudly listed, rather than guessed.
Related, and distinct from each of them
Generated by Claude Code
Filed unassigned by the
domain:devxseat while implementing #11034 (sessionsession_015ahemw8RcTgqtxrj15PEZx). Triage on #11034 ruled this gap is its own gate card and explicitly scoped it out of that PR:Recording only. Not claimed, not queued, not graded.
The gap
{/* os:check */}blocks incontent/docs/**are type-checked bypackages/spec/scripts/check-skill-examples.ts(tsc --noEmitover the extracted blocks). A visibility predicate is authored as a bare assignment whose value is typedstring:visibleWhen: "record.status != 'closed' && user.hasRole('admin')"hasRoleis not a CEL function — it appears in no stdlib registry and on no contract (see below). The line nevertheless type-checks perfectly, because every CEL string is the same type as every other CEL string. That is what let #11034's example ship and survive: a shipped doc taught a visibility gate whose copy faults at runtime and — because visibility is fail-open — shows the element to everyone.Why the sibling gate does not already cover it
packages/lint/scripts/check-doc-formula-expressions.mjscloses exactly this class for field formulas, and its own header states the reasoning this card is a second instance of:It cannot reach
visibleWhenas written, deliberately. Its opt-in is structural and narrow — (A) aField.<anything>({ … expression … })factory call, or (B) an object literal carrying bothtype: 'formula'andexpression. AvisibleWhen:assignment matches neither, and the gate's header is explicit that predicates are out of its scope because their scope depends on enclosing structure a fragment does not carry.The hard part, named rather than hand-waved
A
visibleWhengate cannot use one scope for every site, because the binding root really does differ by layer. Measured onorigin/mainat 365e334 while fixing #11034:visiblerecord,current_user,user,ctx.user,os.user,app,features,page.*(objectuiapp-shell/src/providers/ExpressionProvider.tsx:59,70)visibleWhenrecord+ the host predicate scope (objectuicore/src/evaluator/optionRules.ts:103)visibleWhenrecord+previousonly — all threeresolveFieldRuleStatecall sites in objectuicomponents/src/renderers/form/form.tsx(1201, 1237, 1935) passundefinedfor thescopeparameterSo the same predicate text is correct on one layer and an unbound root on another, and a fragment in a doc fence does not always say which layer it illustrates. A gate keyed on the key alone would produce false reds — the failure mode
check-doc-formula-expressions' header calls out as worse than no gate. Any design here has to answer "which layer is this fragment about" first.Suggested shape (not a decision)
Extend
check-doc-formula-expressions' opt-in rather than minting a second opinion about one contract (Prime Directive #12): avisibleWhen/readonlyWhen/requiredWhenslot whose enclosing structure identifies the layer, validated through@objectstack/formula'svalidateExpressionwith that layer's scope. Sites whose layer cannot be determined statically are skipped, loudly listed, rather than guessed.Related, and distinct from each of them
nameconvention in content/docs/** examples — os:check passes becausenameis.optional()#10830 —os:checkcannot enforce the form-sectionnameconvention. Different mechanism: there the key is declared and.optional(), so the block is genuinely well-typed. Here the value's content is unvalidated.check:skill-examples' marked-block extraction is not fence-aware. Extraction, not semantics.visibleWhengate found by census: one authored-node bypass, a second evaluator with an oppositedatabinding, and total silence on fault in production (all land in objectui) #11258 — three gaps in the runtimevisibleWhengate (all landing in objectui). Runtime enforcement, not the docs corpus.PageComponentSchema.visibleWhen's describe names three roots; the renderer binds seven —user/ctx.user/os.user/app/featuresare undocumented on that surface #11256 — the specdescribeforPageComponentSchema.visibleWhennames three roots where the renderer binds more. Independently corroborates the binding table above.Generated by Claude Code