Uh oh!
There was an error while loading. Please reload this page.
docs(spec): visibleWhen's describe states the bound roots per ADR-0068 (was three of nine) - #11600
Conversation
…ontract from renderer `PageComponentSchema.visibleWhen` named three roots (`record`, `current_user`, `page.<var>`) while the shipping renderer binds nine. Rather than pasting the renderer's list, the describe now separates two different claims: - Contract-bound: `record`, `current_user` with ADR-0068's `user` / `ctx.user` aliases, and `page.<var>`. ADR-0068 D1 rules that a predicate "evaluates identically in a formula, an RLS policy, and a client `visible` gate", and `EvalUser`'s docblock already states the same alias set for "client UI gates" — so these are transcribed from a ruling, not observed from code. - Renderer-provided, NOT guaranteed: `app`, `features`, `os.user`, `data`. ADR-0068's Non-goals fence its ruling to the user object, so nothing rules these on this surface; they are recorded as measured behaviour. - `data` is surface-dependent: the data-source adapter on a component node, the record row on a `page:tabs` item-level `visibleWhen`. The `page:tabs` item `visibleWhen` carried the identical three-root sentence plus a "binds the same environment as page-component `visibleWhen`" claim that measurement disproves; corrected the same way, naming its two real divergences. Describe/prose only — no accept/reject change, no shape change, no new keys. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T9cDbY2NBiVJWYx3BpWfH2
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
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 e9fda9a112bfdbe680ce3c879d2db03d0bee16af && git checkout e9fda9a112bfdbe680ce3c879d2db03d0bee16af
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6d3550961f57d2b5efcbb5de5dd45c0090363d03 a880a63fd66bbf44c6e74e2f029d67415224e899 && git checkout -B drift-repro 6d3550961f57d2b5efcbb5de5dd45c0090363d03 && git merge --no-ff a880a63fd66bbf44c6e74e2f029d67415224e899
node scripts/docs-audit/affected-docs.mjs --json 6d3550961f57d2b5efcbb5de5dd45c0090363d03
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11256
PageComponentSchema.visibleWhen's describe named three roots while the shipping renderer binds nine. The card'sDraft on purpose — path-limb clause-② (
packages/spec/src/**), held for the PM's review. Substantively clause-② is no: describe/prose only, no accept/reject change, no shape change, no new keys.1. The ADR-0068 reading — it does reach this surface, but only for the user object
Two operative quotes settle the user aliases. ADR-0068 D1:
A page-component
visibleWhenis a clientvisiblegate, so the ADR reaches this surface by its own words. And@objectstack/specalready says so itself —packages/spec/src/identity/eval-user.zod.ts, the ADR-0068 contract home:So documenting
user/ctx.userhere is transcribing a ruling this package already makes elsewhere, not code-worship. That is the load-bearing finding:page.zod.tswas under-stating a contract, not merely lagging an implementation. It also explains the card's point 1 — thesys_useralert gate'sctx.useris contract-legal, and the describe was what was wrong.The same ADR fences everything else off. Non-goals:
ADR-0058 was checked as the other candidate and rules dialects, backends and fail-policy — not the UI root set. So nothing rules
app,features,os.userordataon this surface.os.userdeserves its own line, because it is the one that looks ruled and is not. ADR-0068 mentions it only as "the legacyos.userformula namespace" and among aliases "kept … during migration", and both places where the ADR states objectui's conformance (D1 and migration checklist item 5) list exactly three: "ExpressionProviderexposescurrent_user/user/ctx.user".os.useris a real spec spelling on other CEL surfaces (seed-loader identity,plugin-security-advancedconditions) but not a ruled root for a UI predicate — so it sits with the ambient set, not the contract set.Neither branch 1 nor branch 2 alone, and that is the finding. Branch 1 (align to a sanctioned alias set) is right for the user object; branch 2 (honest middle) is right for the rest. Renderer-narrowing (branch 3) was not concluded — no objectui change here, no spec behaviour change.
2. The root list, as it now reads
recordcurrent_useruser,ctx.usereval-user.zod.ts, "client UI gates" by namepage.prefixPageSchema.variables)os.userapp,featuresdataThe describe states the first group as binding and the second as measured-but-not-promised. The widening question — should the contract guarantee all nine? — is left open and is NOT decided here (reported to the PM).
3. The
datadouble-meaning — measured, not inherited from the cardVerified read-only in
/home/user/objectuiat the pin this repo builds against (.objectui-sha=190fbd01d061), and confirmed byte-identical at objectuiorigin/main(f2732afe), so the finding is not an artifact of a stale pin:packages/react/src/SchemaRenderer.tsx:463-471composes{ ...predicateScope, current_user, record?, data: dataSource, page }.datais the data-source adapter (what${data.total}interpolates against).page:tabsitem —packages/components/src/renderers/layout/containers.tsx:450-457builds its own evaluator:{ ...recordData, ...predicateScope, current_user, record: recordData, data: recordData, page }.datais the record row.Same key, two bindings. The describe cannot say that briefly enough to be safe, so it does two things: names the adapter meaning where it applies, and points at the tabs key rather than implying it carries over. The tabs key then states its own environment precisely — following the house precedent already set by
view.zod.ts:1959, which documentsdata's surface-dependence the same way ("Inside a repeaterdatais the ROW, but it is still spelleddata").The tabs evaluator also spreads the row's bare fields, a third undeclared breadth; it is now named, with the precedence noted (ambient scope is spread after the row, so an ambient root wins over a same-named record field).
4. Sibling sweep
Swept
packages/spec/srcfor the same three-root sentence.component.zod.ts:676—page:tabsitemvisibleWhen, carrying the verbatim phrase "Bindsrecord,current_user,page.<var>". Fixed identically. Its JSDoc additionally claimed it "Binds the same environment as page-componentvisibleWhen" — a claim the measurement above disproves on two points, so that sentence was the most wrong text in the sweep and is corrected rather than copied.packages/spec/src/ui/component.zod.ts. Bounded-fix conditions hold — same defect class, correct form pinned by ADR-0068 + measurement, no competing claim (no open PR touches the file; last change was merged fix(spec): COMPONENT_NODE_VISIBILITY_GUIDANCE no longer claims a hoisted properties visibility key is evaluated by nothing #11390), same gate family, no new verification surface.action.zod.ts:323— per-optionvisibleWhen, "Same env as the field-level per-option visibleWhen (record + current_user)"; different surface, different sentence.action.zod.ts:377— action-paramvisibleJSDoc already names its ambient scope ascurrent_user/app/data/features; a different surface (action buttons) that does not under-state the same way.view.zod.ts:1959/:2119— field/sectionvisibleWhen, which deliberately saycurrent_useris NOT bound. Opposite claim, correctly stated; untouched.5. Regen audit
pnpm --filter @objectstack/spec gen:docsproduced exactly one line of change — thevisibleWhenrow incontent/docs/references/ui/page.mdx. No collateral.The tabs describe produced no docs diff, and that is expected rather than a miss:
component.mdxrenders that item shape as a collapsed nested signature (visibleWhen?: string \| object) and emits no nested describe text, so the tabs wording reaches authors through the schema/TS surface, not the MDX table.authorable-surface.base.jsondid not move either — it anchors keys, not prose, which is independent structural confirmation that this change adds no keys.6. Verification
Gate union re-run on the final commit,
a880a63fd6(tree clean at the time of the run), quoting each gate's own verdict line:check:generated—✓ All 14 generated artifacts are up to date.check:docs—✅ 229 generated files in sync with packages/speccheck:objectui-pin-citations—✅ 8 asserting objectui pin citation(s) match .objectui-sha (190fbd01d), 6 historical citation(s) recorded and not checked, across 991 spec source(s).The two new citations use the historical spelling (`.objectui-sha` pin `<sha>`) — a dated measurement record, which is what they are; the asserting form would have claimed something the gate re-checks on every pin bump.check:nul-bytes—check-nul-bytes: OK (scanned 6490 text file(s) … no raw ASCII control bytes).pnpm --filter @objectstack/spec test—Test Files 420 passed (420)/Tests 11213 passed (11213)pnpm --filter @objectstack/spec typecheck—check:test-typecheck: OKpnpm --filter @objectstack/spec build— exit 0Gate list derived mechanically with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(27 families for these paths, + 6 changeset families once the changeset existed). All ran green except one, declared below. Also green:check:cross-package-test-inputs,check:doc-anchors,check:doc-authoring,check:docs-audit-scope,check:docs-redirects,check:merge-driver,check:published-files,check:quick-reference-counts,check:role-word,check:slot-lookup,check:spec-parsed-alias,check:test-source-alias,check:type-source-resolution,check:empty-state,check:liveness,check:strictness-ledger,check:variant-docs,check-ci-filter-parity,check-doc-frontmatter,check-plugin-teardown-shape,check-section-landing-index,check-affected-docs,check:changeset-gate-self-tests,check:objectui-changeset,check-adr-0087-registration,check-changeset-no-major,check-empty-changeset,release-rehearsal-clone --self-test.Declared, not runnable here:
node scripts/check-dev-prereqs.mjs(exit 1). It asserts a precondition, not a verdict on this diff: "66 of 67 workspace packages declare an entry point under dist/ that is not on disk". 67 total minus 66 unmet leaves exactly one built —@objectstack/spec, the package this card touches and the one built here. Clearing it needs a fullpnpm buildof the workspace, a repo-scale run CI performs before its gates.Lint was narrowed, and the narrowing is measured rather than assumed:
.mdxand.mdasFile ignored because no matching configuration was supplied— two files are in population.--format json: 2 files linted, 0 errors / 0 warnings.eslint.config.mjsstates it "never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file", so this diff cannot move the verdict of a file it did not touch.7. Scope
Describe/prose only. No accept/reject change, no shape change, no new keys, no objectui change, no spec behaviour change. Changeset:
patchfor@objectstack/spec.Generated by Claude Code