Uh oh!
There was an error while loading. Please reload this page.
feat(spec): form-view predicates loudly reject the features.* scope root - #12849
Conversation
…oot (#12665) One authored form view is served on two kinds of route, and a features.* predicate got two verdicts from the same text: bound to real auth-config flags inside an app, UNBOUND on the standalone form routes (/forms/:name, public /f/:slug) - where the predicate faults and visibleWhen fails OPEN, showing the feature-gated field to everyone. Ruled 2026-08-27 on objectui#6262 (option B): the vocabulary is narrowed at the authoring door. - FormViewSchema superRefine scans every predicate the form view carries (section-level, field-level at any depth, per-option) for features in root position, string literals stripped; prescriptive refusal names the root, the surface, the fail-open reason and the ruling. - ADR-0087 semantic entry ui-form-view-predicate-features-root-refused; registry regenerated. - Docs: ui/views.mdx boundary callout + describe() strings (references page regenerated). - App-context predicate surfaces are untouched and pinned untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JvjTCjJQn9zSTXEhUKgT7s
📓 Docs Drift CheckThis PR changes 1 package(s): 18 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 25add2ebb700d39867c551e0ec80dfa40944b89a && git checkout 25add2ebb700d39867c551e0ec80dfa40944b89a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6f0fec3d0b8046f9f171ff157a7401fa5293d53f a363c0426b606a52be84522c7f12f516752b57bf && git checkout -B drift-repro 6f0fec3d0b8046f9f171ff157a7401fa5293d53f && git merge --no-ff a363c0426b606a52be84522c7f12f516752b57bf
node scripts/docs-audit/affected-docs.mjs --json 6f0fec3d0b8046f9f171ff157a7401fa5293d53f
|
Uh oh!
There was an error while loading. Please reload this page.
…12849) os-regen-merge.sh step 4: gen:migration-registry rebuilds registry.ts with BOTH step-18 populations (#12849's ui-form-view-predicate-features-root-refused semantic entry beside this PR's retirement + semantic entries), the retirement's manifest and authorable-surface deletions are re-applied over main's side (the merge takes main's copy of os-regen artifacts by design; the #4725/#4650 proofs re-verify against the new base), and the full chain regenerates green: check:generated 14/14, check:llms-txt 208 schemas, the five touched spec suites, client typecheck and the resolver + unwrap pin suites all pass on the merged tree. Co-authored-by: Claude <noreply@anthropic.com>
Fixes#12665
Session of record: https://claude.ai/code/session_01JvjTCjJQn9zSTXEhUKgT7s (os-dev dispatch under the domain:spec seat).
What / why
Implements the maintainer ruling of 2026-08-27 (decision-inbox batch 2, verbatim: 「同意」 — adopting Option B on objectui#6262): form views may not name the
features.*scope root in predicates, and the exclusion is declared and enforced at the authoring/publish door with a loud rejection.One authored form view is served on two kinds of route, and a
features.*predicate got two verdicts from the same text: inside an app (/apps/:appName/*) the root resolves against the real auth-config flags; on the standalone form routes (/forms/:name, public/f/:slug) no app context exists, the root is UNBOUND, the predicate faults — andvisibleWhen's fault fallback is visible, so the field or section a feature flag was meant to hide was shown to everyone (fail-open, on an access-shaped key). The ruling narrows the vocabulary instead of building auth-config parity machinery for zero consumers.Day-of premise re-verification (dev's first action, per the card)
Re-ran the objectui#6262 measurement against objectui
origin/main@38a123cac(2026-08-28, before any code): acrossapps/examples/content/over json/yml/yaml/mdx — positive controlvisibleWhen: 18 hits; targetfeatures.: 3 raw hits, all three the known false positives (two English prose sentences, one JavaScript string inside a code-preview widget). Zero authoredfeatures.*form-view predicates — the ruling's premise holds.Changes
packages/spec/src/ui/view.zod.ts—checkFormViewPredicateFeaturesRootscanner (CEL string literals stripped, thenfeaturesin root position; member access likerecord.features.xstays legal) wired intoFormViewSchema's existing superRefine, covering section-levelvisibleWhen, field-levelvisibleWhenat any nesting depth, and per-optionvisibleWhenauthored inline in the form view — bothsectionsand the legacygroupsbucket (pre-fold), the deprecatedvisibleOnalias spellings (refused post-fold), every formtypeincludingsplit, and the flattened runtime form overlay (refinement carried by.extend()). The refusal is a prescriptivecustomparse issue naming the root, the surface, the fail-open reason and the ruling. Error identity: this rides the standard Zod-issue validation channel (same as the existingpaneandsubmitBehavior.urlrefusals) and surfaces on the wire through the standard validation envelope — no new top-levelerror.codestring is introduced, so nothing enters the ADR-0112 ledger.18.ui-form-view-predicate-features-root-refused+gen:migration-registryregeneration.visibleWhenrows incontent/docs/ui/views.mdx;describe()strings updated (generatedcontent/docs/references/ui/view.mdxregenerated viagen:docs).@objectstack/specminor (accept-set narrowing, per the repo convention — same level as the FormField constraint-shape narrowing;check:changeset-no-majorrespected).packages/spec/src/ui/view-form-features-root.test.ts— 9 refusal cases pinning code/path/message identity (bare-string + envelope, section/field/nested/per-option,groups,visibleOnalias, bracket + bare-root + call-arg forms,type: 'split'), 5 positive controls (permitted roots,record.features.xmember access, longer identifiers, string literals, AST-only envelope's documented pass), 2 app-context pins (page component and bulk-action predicates namingfeatures.*still parse — that surface keepsfeatures.*exactly as-is).⛔ Scope guard: no other predicate surface changed. objectui#6262 remains open — it is unlocked by this landing, not closed by it (that card is
pm:blockedon the card this PR fixes, and it is resolved over on the objectui side only after its own re-verification).Verification (all readings at HEAD
a363c042, after the final commit)node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(7 changed paths vs merge-basef907fbe9e): 37 path-derived families + 6 convention-triggered (new-test-file kind). All measured families green, includingcheck:authorable-surface,check:generated(14/14 artifacts, printed "All 14 generated artifacts are up to date"),check:docs,check:migration-registry,check:spec-changes,check:upgrade-guide,check:engine-double-contract,check:where-matcher,check:cross-package-test-inputs,check:type-check-coverage, andcheck:type-check-debt(printed "OK — 31 ledger entries re-measured … none above its recorded number", run after building the full packages closure exactly as its own refusal prescribes).node scripts/pm/check-half-states.mjs(half-state-patrol flavor) — exit 3, printed "Nothing was swept … it is no reading at all" (needs a GitHub credential this container does not hold; the lint.yml flavorpnpm check:pm-half-states= self-test ran green);check:react-declaration-parity— check:generated's own printed exclusion ("needs MANIFEST … objectui's registry-inputs dump", an input this repo cannot produce; CI owns it).pnpm --filter @objectstack/spec typecheckgreen (main + scripts + test layer; the new test file confirmed inside the tsconfig.test.json population viatsc --listFiles, 1 hit). Repo-widepnpm lint(eslint,--no-inline-config) exit 0 at the same HEAD.check:nul-bytesgreen (7141 files).features.invisibleWhen/visibleOnfixtures — the only hits are action-param fixtures (action.test.ts), an app-context surface this PR does not touch and pins untouched.grep -cof the marker = 1 at the anchor); refusal suite on the mutated tree: 9 failed / 7 passed — exactly the nine refusal cases turned red (bad predicates parsed successfully), the seven positive/app-context controls stayed green; restore viagit checkout HEAD -- path, proved by blob equality (git hash-object= HEAD blobbc2074d3…, marker count 0,git diff HEADempty); restored tree re-run: 16/16 green. No rebuild was needed for either leg — the suite imports./view.zodas an in-package relative source import, so no dist/exports resolution is in play (stated per the ablation contract); the spec package was nonetheless rebuilt afterwards because the mutate+restore touched the source mtime and the dist-freshness preflights (check:api-surface,check:skill-examples) refuse on mtime — both green after rebuild.Generated by Claude Code