fix(spec,lint): gate unresolvable filter placeholders; freeze the {current_user_id} vocabulary (#3574)#3594
Merged
Merged
Conversation
…ate unresolvable placeholders (#3574) A dashboard widget filtered on `{current_user}` rendered `0` — not an error, a zero indistinguishable from a metric that is legitimately empty, with nothing in the console or the server log. `service_dashboard.my_open_cases_by_priority` in the HotCRM template had shipped broken this way since the day it was written. The token had never been part of the contract. Date macros were frozen in `date-macros.zod.ts` with a spec vocabulary, a lint-usable predicate, and one client resolver; `{current_user_id}` had only prose in an `app.zod.ts` JSDoc and three ad-hoc client implementations, each handling one surface's filter shape. Nothing could tell an author their token was wrong. - spec: new `data/context-tokens.zod.ts` freezing CONTEXT_TOKENS (current_user_id, current_org_id) as the sibling of DATE_MACRO_TOKENS, with isContextToken / isKnownFilterToken / classifyFilterToken and a CONTEXT_TOKEN_SUGGESTIONS near-miss table. Documents what the tokens are NOT: presentation scope, never an access boundary — that is RLS, which uses the unrelated `current_user.id` expression root. - lint: new `validateFilterTokens` (rule `filter-token-unknown`, error). Walks filter / filters / runtimeFilter subtrees across dashboards, objects, views, reports, datasets, pages and apps. It scans for filter KEYS rather than enumerating known surfaces, so a new surface following the convention is covered the day it ships — enumerating surfaces is how the dashboard was missed in the first place. Navigation recordId / params stay out of scope: they resolve AppContextSelector ids, meaningless in a filter. - cli: the gate runs in `os validate` and `os compile`. Error rather than warning because of who authors this metadata. An AI reads a query returning 0 as a correct answer and builds on it; its correction loop is author -> validate -> fix, so a diagnostic only reaches it if it can fail the build. The three spellings the suggestion table covers — {current_user}, {user_id}, {organization_id} — are each correct somewhere else in the platform, which is exactly why authors reach for them. Also fixes a ViewSchema JSDoc example documenting `{user_id}`, which resolves nowhere, and adds the Context Tokens reference to the objectstack-ui skill. Runtime resolution ships in objectui (@object-ui/core resolveFilterPlaceholders). Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 110 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ports (#3574) The public-API ratchet flagged 10 additions, 0 breaking — the new context-token vocabulary. Snapshot regenerated with `pnpm --filter @objectstack/spec gen:api-surface`. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3574 (framework half). Runtime resolution ships in objectstack-ai/objectui#2857.
The bug behind the bug
The reported symptom is a dashboard widget filtered on
{current_user_id}rendering0. Investigating it turned up something worse than a missing substitution:{current_user}— the spelling in the first repro row — has never existed anywhere.current_useris the RLS expression root (owner_id = current_user.id), a genuinely server-side mechanism. It was never a filter token. Only{current_user_id}is real.date-macros.zod.ts— a spec vocabulary, a lint-usable predicate, one client resolver.{current_user_id}had prose in anapp.zod.tsJSDoc and three ad-hoc client implementations, each handling one surface's filter shape.DatasetWidget,ObjectMetricWidget,ObjectDataTable,ObjectPivotTable,ObjectChart(both paths).skills/objectstack-ui/SKILL.mdpublished a widget example using the token (which didn't work), andview.zod.tsdocumented{user_id}, which resolves nowhere.What this PR does
Brings
{current_user_id}up to the maturity date macros already had, and adds the gate that would have caught this at authoring time.@objectstack/spec—data/context-tokens.zod.tsfreezesCONTEXT_TOKENS(current_user_id,current_org_id) withisContextToken/isKnownFilterToken/classifyFilterTokenand aCONTEXT_TOKEN_SUGGESTIONSnear-miss table. The module is explicit that these are presentation scope, never an access boundary.@objectstack/lint—validateFilterTokens(rulefilter-token-unknown, severityerror) walksfilter/filters/runtimeFiltersubtrees across dashboards, objects, views, reports, datasets, pages and apps.@objectstack/cli— the gate runs inos validateandos compile.Two design choices worth reviewing:
It scans for filter keys, not known surfaces. Enumerating surfaces is exactly how the dashboard got missed; a new surface following the convention is now covered the day it ships. Navigation
recordId/paramsstay deliberately out of scope — they resolveAppContextSelectorids, which are meaningless in a filter.It is an error, not a warning. The runtime failure is silent and indistinguishable from a genuine zero, so it survives human review. It is worse for AI authors: an AI reads a query returning
0as a correct answer and builds on it, and its correction loop is author → validate → fix — a diagnostic only reaches it if it can fail the build. Each spelling in the suggestion table ({current_user},{user_id},{organization_id}) is correct somewhere else in the platform, which is why authors reach for them.Verification
{current_user}into the app-todo dashboard failsos validate, naming dashboard + widget + path and suggesting{current_user_id}.{today}in the same filter correctly passes.packages/spec6734 tests,packages/lint359 tests, all passing.tsc --noEmitclean on all three packages.🤖 Generated with Claude Code