Uh oh!
There was an error while loading. Please reload this page.
docs(formulas): pin the raw predicate example to the object-field layer - #11672
Conversation
The `### Predicate` fence opened with a bare `{ name, type, visibleWhen }`
object literal. Nothing in that shape says which layer it is written against,
and `visibleWhen` is one key spelled on layers that do not share a scope, so
neither a reader nor `check:doc-formula-expressions` could tell whether the
predicate was meant to bind `record`, a flow screen's flattened field names, or
a page component's user roots.
Wrap it in `ObjectSchema.create({ fields: { … } })`. The `fields:` MAP is the
schema-backed statement of the object-field layer (`ObjectSchema.fields` is
`z.record(name, FieldSchema)`, while every UI/flow layer spells `fields:` as an
array), so the example now says structurally what it previously only implied by
position on the page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZxos-steve
commented
Aug 24, 2026
ACCEPT — and you falsified two of my rulings, one of which I issued as a binding ⛔. Both confirmed here before I ruled.
|
Uh oh!
There was an error while loading. Please reload this page.
Part of #11651
Re-authors the one
*Whendoc fragment that was skipped purely for an authoringreason, so the gate added in #11407 now judges it. The card asked for four; the
measurement below says only one of the four was in that class, and re-authoring
the other three would have mis-labelled the layer they document. That correction
is the substantive result of this run, so it is reported rather than folded in.
The pinned counts that moved — and the ones that turned out not to exist
Runtime counts (moved, as predicted):
Measured, same command both sides (
node packages/lint/scripts/check-doc-formula-expressions.mjs):--self-testpins: nothing needed flipping, and nothing was deleted. Thebrief for this card expected
13 admitted / 7 skippedto be pinned in--self-test, so that reaching green by deleting assertions was the risk toguard against. It is not pinned there.
FIELD_RULE_SELF_TEST_CASESholdssynthetic fixtures that assert the discriminator's behaviour on given code
shapes —
expect: { admitted: 1, errors: 0 }and so on — never a corpus total.The corpus counts exist only in the scan's printed summary. Verified two ways:
--self-testreports48 cases passedunchanged on both sides of this diff,and no pin of those totals exists anywhere outside the script either (searched
the tree for references to this gate; the only ones are
package.json,lint.yml,dispatch-gates.mjsand prose).So this PR changes no line of
packages/lint. In particular the fixtureis deliberately kept. It is the shape this PR just removed from the corpus,
which makes that fixture the only remaining guard that the discriminator still
refuses it — exactly the assertion that must not be dropped. The pin that the
skip list prints on green runs is likewise untouched, and the after-run above
shows the list still printing.
The four, judged BEFORE re-authoring
The card and its triage both assumed all four were correct documentation that
merely carried no layer. Judging them first — through
judgeFieldRule, the samecomposed verdict the gate applies — says otherwise for three of them.
content/docs/data-modeling/formulas.mdx:279content/docs/protocol/objectui/layout-dsl.mdx:821content/docs/protocol/objectui/layout-dsl.mdx:824content/docs/protocol/objectui/layout-dsl.mdx:863The layer column is not inferred — each fragment states it in its own comment,
one line above the predicate:
layout-dsl.mdx:863is the load-bearing one. Its predicate is'sales_manager' in current_user.positions, and the field-level rule returns:That is the same error, on the same text, that
content/docs/ui/pages.mdx:165would produce — the skip the ruling protects by name as a false red on correct
documentation. So
:863belongs in that protected class, not in theauthoring-shape class. Its own page says so twice more: the table at
layout-dsl.mdxline 832 and the prose at line 866 both state thatcurrent_useris not bound on form sections and form fields.:821and:824are a matched pair whose entire purpose is to contrast twolayers' bindings — PageComponent (binds
recordandcurrent_user) againstFormSection/FormField (binds
recordonly). Authoring either half as aField.*factory or afields:map would state that it is an object field,which is a different layer from the one the line exists to illustrate, and would
destroy the contrast that is the fence's whole content. The file is the objectui
layout DSL: it documents the UI layer and contains no object-field example at
all. A form field is additionally the
fields:array case, which thediscriminator refuses by construction.
So the honest reachable total is 14/6, not the 17/3 the card projected. The
remaining six skips are all genuinely other-layer, which is a stronger result for
#11407's discriminator than the card claimed: after this change, every site the
gate declines to judge is one where judging it would be wrong.
The discriminator was not widened, and no gate logic changed.
The change
content/docs/data-modeling/formulas.mdx, one fence. The raw fragment wasand is now wrapped so the layer is structural rather than positional:
The
fields:map is what carries the claim, and it is schema-backed ratherthan stylistic:
ObjectSchema.fieldsisz.record(name, FieldSchema), whileFormFieldSchemaandScreenFieldConfigSchemaare bothz.array(…). A shortprose paragraph now states the same thing for human readers, which was the
argument for doing this at all — the sibling
Field.text({ … })fragment belowit was already admitted, so the raw spelling was the only one on the page whose
scope a reader had to guess.
Occurrence count: 24 raw, 23 in TS fences, 20 sites
The card's report said 23 text-level occurrences; an independent count said 24.
Both are right at their own level, and the difference is fully accounted for:
*When:occurrences in.md/.mdxacross the gate's fourROOTScontent/docs/releases/v17.mdx:414, which sits in adifffence(opened at line 409), and the gate reads only
ts/typescript/tsxfences —so it is invisible to the gate by construction. It is also a releases page,
which is not editable from a code PR regardless.
docs/adr/0089-unify-visibility-predicate-naming.mdlines 44-46, whichquote
ExpressionInputSchema.optional()— the schema, not a predicate, andcorrectly not sites
Verification
Gate families derived, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack— 17families matched for
content/docs/data-modeling/formulas.mdx. All 17 runlocally and green at
daefd8b4, exit codes captured before any pipe. Verdictlines quoted from the gates themselves:
✓ check:doc-formula-expressions self-test: 48 cases passed(48 both sides)✓ check:doc-formula-expressions (field-level *When, #11407): 14 predicate(s) … 6 skipped✓ doc authoring guard: 389 files clean — no bare metadata literals.✅ check-doc-anchors: 278 internal #fragment link(s) across 408 source file(s) all resolve to a real heading✓ check-doc-frontmatter: 403 page(s) under content/docs parse with yaml@2.9.0check-nul-bytes: OK (scanned 6519 text file(s) … no raw ASCII control bytes)check:docs-audit-scope,check:docs-redirects,check:role-word,check:published-readme-links,check:react-page-adapter-contract,check:cross-package-test-inputs,check:section-landing-index,check:doc-security-posture, and the four spec-liveness families(
check:empty-state,check:liveness,check:strictness-ledger,check:variant-docs) — all green.Non-vacuity. The newly admitted site is proven to be judged, not merely
counted. Mutating its predicate
record.statusto a barestatusin placeturned the gate red, naming the site and the arm that admitted it:
The mutation ran under
trap … EXIT INT TERM; the restore was verifiedbyte-identical by sha256 (
6e7ac284…before the mutation and after the restore),and the gate returned to green at 14/6.
No changeset —
skip-changeset, against the rule as writtenChecked in
.github/workflows/pr-automation.ymlrather than assumed. Thechangeset-checkjob has no path-based exemption; its only two are theskip-changesetlabel and the Changesets release PR, and the workflow states thelabel's meaning as "this PR declares no release of its own". This diff is one
file under
content/docs/. No workspace package includescontent/in itspublished files, and
apps/docsisprivate: true, so nothing this PR touchesis published and there is no package a changeset could honestly name — every
changeset in
.changeset/names a workspace package, and naming one here wouldversion a package whose code did not change.
Generated by Claude Code