Skip to content

fix(spec,lint): gate unresolvable filter placeholders; freeze the {current_user_id} vocabulary (#3574) - #3594

Merged
os-zhuang merged 2 commits into
mainfrom
fix/dashboard-widget-current-user
Jul 27, 2026
Merged

fix(spec,lint): gate unresolvable filter placeholders; freeze the {current_user_id} vocabulary (#3574)#3594
os-zhuang merged 2 commits into
mainfrom
fix/dashboard-widget-current-user

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

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} rendering 0. Investigating it turned up something worse than a missing substitution:

  • {current_user} — the spelling in the first repro row — has never existed anywhere.current_user is 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.
  • The token was never part of the contract. Date macros are frozen in date-macros.zod.ts — a spec vocabulary, a lint-usable predicate, one client resolver. {current_user_id} had prose in an app.zod.ts JSDoc and three ad-hoc client implementations, each handling one surface's filter shape.
  • Six widgets were broken, not oneDatasetWidget, ObjectMetricWidget, ObjectDataTable, ObjectPivotTable, ObjectChart (both paths).
  • Our own docs taught two of the wrong spellings.skills/objectstack-ui/SKILL.md published a widget example using the token (which didn't work), and view.zod.ts documented {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/specdata/context-tokens.zod.ts freezes CONTEXT_TOKENS (current_user_id, current_org_id) with isContextToken / isKnownFilterToken / classifyFilterToken and a CONTEXT_TOKEN_SUGGESTIONS near-miss table. The module is explicit that these are presentation scope, never an access boundary.
  • @objectstack/lintvalidateFilterTokens (rule filter-token-unknown, severity error) walks filter / filters / runtimeFilter subtrees across dashboards, objects, views, reports, datasets, pages and apps.
  • @objectstack/cli — the gate runs in os validate and os 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 / params stay deliberately out of scope — they resolve AppContextSelector ids, 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 0 as 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

  • Gate proven red on real metadata: injecting {current_user} into the app-todo dashboard fails os validate, naming dashboard + widget + path and suggesting {current_user_id}. {today} in the same filter correctly passes.
  • Gate proven green: all three in-repo examples pass (3 dashboards, 28 pages, 4 views, 4 reports) — no false positives.
  • packages/spec 6734 tests, packages/lint 359 tests, all passing. tsc --noEmit clean on all three packages.

🤖 Generated with Claude Code

…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>
@vercel

vercelBot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredJul 27, 2026 8:59am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/lint, @objectstack/spec.

110 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx(via @objectstack/cli)
  • content/docs/api/environment-routing.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/cli, packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/backup-restore.mdx(via @objectstack/cli)
  • content/docs/deployment/cli.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/deployment/self-hosting.mdx(via @objectstack/cli)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/cli, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/permissions/authentication.mdx(via @objectstack/cli)
  • content/docs/permissions/authorization.mdx(via @objectstack/lint, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx(via @objectstack/cli)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…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>
@os-zhuang
os-zhuang merged commit 5b89711 into mainJul 27, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the fix/dashboard-widget-current-user branch July 27, 2026 09:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:dataprotocol:uisize/xlteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard widget filters do not interpolate {current_user_id} — user-scoped widgets silently render 0

1 participant

@os-zhuang