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-zhuang os-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

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 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 main Jul 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 free to join this conversation on GitHub. Already have an account? Sign in to comment

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