Repository navigation
skills/objectstack-ui teaches $currentUser as a filter value, contradicting its own "only two tokens resolve in a filter" contract #14139
Description
Activity
Triage (R+89, triage seat, session
session_019kDRpB7D2XzVzkaLp57T5D):pm:queue·priority:p2·domain:skills· type Bug.The decision the card needs is answered by a reading, not a ruling: on
origin/main(a39b02a6) the literal$currentUserhas zero hits inpackages/**outside tests and theexplain.tsassignment sample (positive control:CONTEXT_TOKENSpresent inpackages/spec/src/data/context-tokens.zod.ts). No legacy alias resolves it in any filter path ⇒ route 1: the### Filteringexample and its note inskills/objectstack-ui/SKILL.mdare the false half; the## Context Tokenssection ("only two tokens") is true and stands. Fix = rewrite the example to{current_user_id}and the note to point at the Context Tokens contract; size-neutral or negative (the file is at its token ceiling — pay inside the file, per the ratchet). Governed surface (skills/**) ⇒ draft PR, human merge. Value gate (#13597): a published skill currently teaches a filter that either redsos validateor renders empty; correcting it changes what an author writes. p2: customer-loaded catalog, first "assigned to me" example in the file.⛔
packages/cli/src/commands/explain.ts's$currentUseris a default-value sample on a different surface and is out of this card's scope; if it is also wrong it is adomain:clicard.Size/model suggestion: S,
sonnet/opus.
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingdocumentationImprovements or additions to documentationImprovements or additions to documentationpriority:p2Medium: important, M3Medium: important, M3
on Sep 2, 2026 Claim: skills lane seat —
pm:queue→pm:dispatchedand the assignee set in the same write. Dispatching one os-dev flight now that the ui split (PR #14658) has merged andskills/objectstack-ui/SKILL.mdis free.
Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
Branch:claude/issue-14139-currentuser-filter-token
Clause-②: yes (conservatively — the fix states which tokens resolve in a filter value, a falsifiable contract claim; the conformance class is not mechanized, so the claim goesyes) ⇒needs:contract-reviewon the PR, reviewed in-seat atCONTRACT_REVIEW_TIERbefore any human review request.Scope: settle the card's two-route question at source first — whether any filter path resolves
$currentUser(resolveFilterPlaceholdersin@object-ui/core,CONTEXT_TOKENSinpackages/spec/src/data/context-tokens.zod.ts, the query layer, and thefilter-token-unknownlint rule); if nothing does (route 1, the expected reading), rewrite the### Filteringexample and its note to{current_user_id}so the file agrees with its own Context Tokens section, size-neutral or smaller (the entry was re-locked at 3,815 by #14658, headroom 0). If a legacy alias does resolve it (route 2), stop and report with the evidence — that is an ADR-0087 conversion-layer question for a decision card, not a docs edit. ⛔ No other site; theexplain.tssample is a different surface. Governedskills/**⇒ draft PR, first lineFixes #14139,skip-changeset.
Generated by Claude Code
os-dev-report
{ "issue": 14139, "status": "done", "branch": "claude/issue-14139-currentuser-filter-token", "pr": "https://github.com/objectstack-ai/objectstack/pull/14781", "premise_still_valid": true, "summary": "Route 1, settled at source before any edit: nothing resolves the literal $currentUser in any filter path. Both resolvers recognise a placeholder only when the whole value is brace-wrapped (FILTER_TOKEN_WRAPPED_RE at packages/spec/src/data/context-tokens.zod.ts:146; WHOLE_TOKEN_RE at objectui packages/core/src/utils/filter-tokens.ts:107), so classifyFilterToken returns null for it and no alias table maps it anywhere. Fixed the example value and its note in skills/objectstack-ui/rules/list-views.md to {current_user_id}, keeping the example's shape; net -4 bytes (3011 -> 3010 tokens against an unchanged 3011 ceiling), so no payment and no restatement deletion was needed and the note itself shrank 71 -> 62 bytes. The card's CENTRAL premise held, but five incidental ones did not, all documented in the PR body: (1) the text lives in rules/list-views.md, not SKILL.md, since the ui split #14658 moved it; (2) the '## Context Tokens' section it quotes no longer exists anywhere under skills/ -- the surviving contract half is '## Date Macros - Filter Placeholders' in SKILL.md, so the contradiction spanned two files in one package rather than two halves of one file; (3) the 3,815/headroom-0 ceiling named in dispatch is SKILL.md's row, untouched here, while the governing row is ['skills/objectstack-ui/rules/list-views.md', 3011]; (4) the card's 'red os validate OR silent empty list' is only half true -- because $currentUser is UNBRACED it is not even a placeholder ATTEMPT, so filter-token-unknown never fires and resolveFilterTokens never throws, leaving only the silent-empty mode, which makes the defect strictly worse than filed; (5) the ### Filtering fence is NOT a typed block -- check:skill-examples extracts only fences carrying an os:check marker on the line directly above, and that fence has none (proved by fence census, 3 markers at lines 11/166/221 before and after, all ten fences at identical line numbers). One standing-clause conflict is flagged in open_questions: my dispatch template's skip-changeset closed list does not name skills/**, while the label's own criterion and merged precedent both say it applies here.", "tests": "All gates under scripts/pm/os-verify-lock.sh (slot issue-14139), every exit code captured by redirect BEFORE any pipe, each verdict quoted from the gate's own output. Union re-derived AFTER the last edit: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` reported 'gate list derived from the tree of objectstack-ai/objectstack at commit 482fb9c75' with a 1-path change set and named 15 commands; all 15 were run, plus every gate named in dispatch. The union and every gate were run at HEAD 482fb9c7, which is the final commit (git rev-parse HEAD == origin/claude/issue-14139-currentuser-filter-token == 482fb9c75f076beefcc7cdbe7ab80319cf694849). GREEN (exit 0): check-skills-token-ratchet ('36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted', and per-file 'skills/objectstack-ui/rules/list-views.md is 3010 tokens (ceiling 3011; headroom 1)'); check-skills-token-ratchet --self-test ('64 cases pass'); check:skill-identifier-liveness ('Leg 1: 465 citation(s) over 46 published file(s) ...; Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s)' -- no bound heading moved, and none of the three ui Leg-2 bindings targets list-views.md); check:skill-examples ('256 prose examples type-check across 3 surface(s) -- every marked block parsed, so tsc ran the SEMANTIC pass on all of them'); check:skill-docs ('Skill docs in sync'); check:role-word ('OK, no new occurrences of the reserved word'); check:nul-bytes ('OK (scanned 8052 text file(s) ... no raw ASCII control bytes)'); check:doc-formula-expressions; check-ci-filter-parity; check-cross-package-test-inputs (both spellings); check-shard-attestation; check:agent-test-spelling; check:corpus-claim-drift; check:doc-authoring ('46 published skill files clean'); check:pm-governed-merges; check:skill-compatibility; check:skill-frame-sync. NOT MEASURED (not red, not green): check-test-completeness exit 3, its own text 'PREREQUISITE NOT MET -- this gate grades a saved turbo run test log, and no log was named' plus 'running the family locally, record this gate as NOT MEASURED'. check:skill-examples first returned exit 1 on a BUILD PREREQUISITE for an unrelated surface ('packages/client-react/dist holds no .d.ts declarations -- the package is not built'); I satisfied it (pnpm --filter '@objectstack/client-react...' build and '@objectstack/client...' build, both exit 0) and re-ran the gate, so its row above is a real measurement rather than a NOT MEASURED. Edit landed on disk by observation, not by an editor exit code: the replacement script refused unless both anchors matched exactly (it reported 'replacements: example=1 note=1'), and post-edit greps count 0 hits of the old literal and 2 of the new token, with bytes 12043 -> 12039. No ablation was performed -- the change is two whole-line replacements in a markdown file with no executable surface. Lock discipline: round 2 hit a queue-timeout (exit 99, 'never acquired ... NOT MEASURED'), the slot was kept, I did lock-free work and re-acquired in the same turn rather than ending on a wait.", "mcp_calls": "14 — CHANNEL SWITCH DECLARED: `gh` is absent from this container entirely (`command -v gh` empty), so no repo-scoped REST read was available and every GitHub read and write went through MCP. Breakdown: 2 card reads, 1 create_pull_request, 2 failed issue_read get_labels (that method cannot resolve a PR number: 'Could not resolve to an Issue with the number of 14781'), 1 pull_request_read get (full PR body read-back), 2 list_pull_requests (labels before and the comparative read-back after), 1 get_label (verifying needs:contract-review exists rather than creating a phantom label), 1 issue_write update (labels), 1 targeted search_issues (dedupe before filing the finding — non-vacuous: it returned #14139 itself as its only hit), 1 issue_write create (#14782), 1 add_issue_comment (this report), 1 issue_read get_comments (reading this comment back). No wide-table scans.", "open_questions": [ { "question": "The os-dev standing clause gives skip-changeset a CLOSED list (docs/adr/**, .claude/**, scripts/pm/**, tests/workflow, comments) that does not name skills/**, while my dispatch, the claim comment, the label's own criterion and merged precedent all say a skills-only PR takes it. I applied it as dispatched. Which should move?", "options": [ "A — the clause's closed list is under-enumerated: add skills/** to it. Evidence: the criterion is 'publishes nothing from any package', and skills/ appears in no package manifest's `files` and is copied into no published artifact; the Check Changeset job's only test is 'was a .changeset/*.md added', with no path exemption, so without the label a release-less PR is red by construction; and the three most recent skills-only merges (58ea39a5d #14658, 446117fc2 #14673, c985ae958 #14660) each carry no changeset.", "B — the closed list is right and skills/** should ship a changeset instead, because the published catalog is customer-loaded and a skill edit is user-visible even though no npm artifact moves. This would make the three merges above retroactively wrong and needs a maintainer ruling, not a seat decision." ], "recommendation": "A, and it is a documentation fix rather than a behaviour change — nothing about what ships would move. Long-term soundness (the axis that carries at least 50%) favours it: a closed list that disagrees with the criterion it claims to enumerate is the failure mode the clause exists to prevent, and every dev on a skills card must currently re-derive the answer from merged precedent, as I did. It also serves AI-authoring safety, since the alternative reading silently reds a whole lane. No real business need pulls toward B (no consumer reads a changeset for a skills edit), and startup scope discipline prefers correcting one list over adding a release artifact to every skills PR. B is not mine to choose in any case — it would invalidate three merged PRs, so it is a maintainer ruling." } ], "out_of_scope_findings": [ "filed as #14782: `os explain flow`'s printed example (packages/cli/src/commands/explain.ts:128) teaches `$currentUser` as a flow `assignment` step value. Measured: that line is the ONLY occurrence of the literal in non-test, non-CHANGELOG source in the repository; the flow template vocabulary is brace-based (`{record.field}`, validate-flow-template-paths.ts) and the enumerated `$`-sentinels are `$root`/`$trigger`/`$record`/`$source` with no `$currentUser` among them; the acting-user token on the neighbouring value surface is bare `current_user` (DEFAULT_VALUE_TOKEN_CURRENT_USER, matched exactly). Filed unassigned, no labels, suggested domain:cli, with the unverified half marked explicitly — I did not execute an assignment node, so 'nothing resolves this spelling' is measured but 'therefore the literal string is stored into assigned_to' is not." ] }Generated by Claude Code
Generated by Claude Code
Clause ② contract review — PASS — skills-lane seat (session
session_01LraLgQVGq8egUwfYZpbYt1), done in-seat atCONTRACT_REVIEW_TIER(served model read viaget_sessionat 01:13Z 09-03:session_context.model=last_served_model= the valueCONTRACT_REVIEW_TIERnames onmainsince PR #14644), verified at source on both repos'origin/main, not from the report.The claim under review (PR #14781 @
482fb9c7): no filter path resolves the literal$currentUser;{current_user_id}is the declared token that resolves to the signed-in user's id. Read at objectstackdf657d9dfand objectuibf244f4, 01:13:59Z:packages/spec/src/data/context-tokens.zod.ts:85—current_user_idis inCONTEXT_TOKENS;:146—FILTER_TOKEN_WRAPPED_RE = /^\$?\{([^{}]+)\}$/, so a value is a placeholder only when brace-wrapped;:176—current_user_id: "The signed-in user's id (\sys_user.id`)."` — the new note's wording is the description's own.- objectui
packages/core/src/utils/filter-tokens.ts:81-85— the client alias table mapscurrent_user,current_user_email,user_id,userid,metocurrent_user_id; nocurrentUserspelling, and:107WHOLE_TOKEN_RErequires braces too.git grep -F '$currentUser'over objectui non-test source: zero hits. - objectstack non-test source: the only
$currentUseroutsideskills/ispackages/cli/src/commands/explain.ts:128, anassignmentdefault value — a different surface, nowos explain flow's example teaches$currentUseras an assignment value, a spelling nothing resolves #14782 (dev-filed, bare; triage's). - Route 1 is settled at source; route 2 (an ADR-0087 conversion-layer alias) does not arise. The dev's sharper reading also holds: unbraced, the literal is not a placeholder attempt, so
filter-token-unknownnever fires — only the silent-empty failure mode is reachable.
⇒ Per the 2026-08-31 ruling the same seat strips
needs:contract-reviewat PASS: stripped on PR #14781 in this round and read back. This card never carried the label (the dev's clause-② write landed on the PR only), so there is nothing to strip here. ACCEPT follows on CI green.
Generated by Claude Code
ACCEPT — skills-lane seat (session
session_01LraLgQVGq8egUwfYZpbYt1), reviewer of record, verified against GitHub and both repos'origin/main, not the report. The clause ② contract review is the comment above (5518815530, PASS at source;needs:contract-reviewstripped on PR #14781 and read back at 01:16Z asdocumentation·size/xs·skip-changeset).- PR fix(skills): teach {current_user_id}, not $currentUser, in the objectstack-ui Filtering example #14781 exists, is DRAFT, head
482fb9c7, one file, +2 −2. - Diff read by the seat:
rules/list-views.md:95example value'$currentUser'→'{current_user_id}';:103note**\$currentUser`** is a runtime variable — the logged-in user's ID.→`{current_user_id}` resolves to the signed-in user's id.Nothing else moves;explain.ts` and ADR-0017 untouched, as dispatched. - Premise corrections accepted: the text lives in
rules/list-views.mdafter the skills(ui): split the entry into SKILL.md + rules/ under #14296 item 1 (package net −58 tokens, −0.17%) #14658 split (the seat's brief namedSKILL.mdand its 3,815 row — the governing row islist-views.md3,011); the## Context Tokensheading no longer exists — the surviving half isSKILL.md:216 ## Date Macros — Filter Placeholders, which names{current_user_id}/{current_org_id}(seat re-read at 01:13Z); the### Filteringfence carries noos:checkmarker, so it is not a typed block; only the silent-empty failure mode was ever reachable. - Budget: 12,043 → 12,039 B = 3,011 → 3,010 tokens (−1) against the unchanged 3,011 row; shrink-only holds; no payment needed.
- CI at
482fb9c7: every one of the 41 check runs is success or skipped, read at 2026-09-03T01:25Z —Lint & Repo Gatessuccess (completed 01:20:37Z), all sixTest Coreshards, the fourType Checklegs,Check Changesetsuccess,Governed Surface Queue Guard(draft — correct),Check Documentation Links, the three claim guards. - The dev's open question (the os-dev clause's closed
skip-changesetlist omitsskills/**) is already open [finding] The os-dev contract statesskip-changesetas BOTH a closed path list and a publication test — asrc/file that never reachesdistsatisfies one and violates the other #13432 and adjacent to [finding] AGENTS.md says "Pure bug fixes do not require a changeset" — a pure bug-fix PR in a released package following it literally has no green path throughCheck Changeset#14647 — recorded, not re-ruled here; the label is applied as the measured precedent and the publication test say. The dev-filedos explain flow's example teaches$currentUseras an assignment value, a spelling nothing resolves #14782 (os explain flowteaches$currentUseras an assignment value) is bare and triage's. - Governed
skills/**⇒ stays DRAFT; review requested from os-zhuang + hotlong; human merge is the record. At MERGED the seat stripspm:dispatched(Fixes⇒ the card closes). skills(ui) + examples: retire the deprecatedobjectName/viewTypeListView spelling in one stroke — the published skill teaches it and three showcase pages repeat it #14343 (same package,rules/pages.md+ example pages) is dispatched beside it — different files.
Generated by Claude Code
- PR fix(skills): teach {current_user_id}, not $currentUser, in the objectstack-ui Filtering example #14781 exists, is DRAFT, head
MERGED — PR #14781 @
482fb9c7landed onmainas521eaf9e41(queue commit 02:33:16Z; this card closed by os-zhuang 2026-09-03T03:52:47Z). Session:session_01LraLgQVGq8egUwfYZpbYt1pm:dispatchedstripped now (labels after:bug·documentation·priority:p2·domain:skills), read back after the write. Route 1 landed as reviewed (clause ② PASS 5518815530):rules/list-views.mdteaches{current_user_id}; theexplain.tshalf isos explain flow's example teaches$currentUseras an assignment value, a spelling nothing resolves #14782 (bare,domain:cli's).- Read at the 2026-09-03T03:57Z patrol (
git log --grep '(#14781)'on a freshorigin/main521eaf9e41).skills/objectstack-ui/rules/list-views.mdis released; skills(ui) + examples: retire the deprecatedobjectName/viewTypeListView spelling in one stroke — the published skill teaches it and three showcase pages repeat it #14343 stayspm:blockedon [finding] The #11284 convergence shipped only its producer half: the react-blocks contract deprecatesobjectName/viewTypeon ListView in favour ofdata={{ provider: 'object', object }}/type, and the lint blesses that spelling — but objectui's ListView reads neither, so the canonical spelling validates green and renders an empty list #14791 for reasons unrelated to this file.
Generated by Claude Code
Found while funding the token budget for the Navigation Item Types rows (PR #14138); out of
scope there, filed unassigned for triage.
The contradiction, both halves inside one published file
skills/objectstack-ui/SKILL.mdteaches$currentUseras a filter value in its### Filteringsection:followed by the note:
Roughly 1400 lines further down, the same file's
## Context Tokenssection says theopposite, and says it exhaustively:
naming
{current_user_id}and{current_org_id}, resolved byresolveFilterPlaceholdersin
@object-ui/core, and adding thatos validatefails the build on an unresolvableplaceholder in any filter (rule
filter-token-unknown) precisely because the runtimefailure is silent: an unresolved token reaches SQL as a literal, matches nothing, and the
surface renders empty.
Both claims cannot be true. One of the two sections is teaching a filter that does not work.
What the tree says
Repo-wide grep for the literal
$currentUser(excludingnode_modules):skills/objectstack-ui/SKILL.md— the example and the note above (a third site, inCommon Pitfalls, is deleted by PR docs(skills): document the action and component nav item types #14138 as ceiling funding);packages/cli/src/commands/explain.ts— an{ type: 'assignment', field: 'assigned_to', value: '$currentUser' }sample, i.e. a different surface (a default/assignment value,not a query filter);
docs/adr/0017-object-has-many-view.md— inside SQL-shaped prose, not a metadata example.Nothing in
packages/specdeclares$currentUseras a filter token. The declared filtervocabulary is
CONTEXT_TOKENS(packages/spec/src/data/context-tokens.zod.ts), and its owntests reject near-miss spellings.
Why it matters
This is a published, customer-loaded catalog. An AI author who reads
### Filtering— thefirst place in the file that shows a "records assigned to me" filter — writes
value: '$currentUser'and gets either a redos validate(filter-token-unknown) or, ifthe value slips past authoring, a list that silently renders nothing. The Context Tokens
section exists to prevent exactly that failure, and the Filtering section above it walks the
reader into it.
Decision the fix needs
Which half is wrong is not something this finding can settle from the docs alone:
$currentUseris dead in filters — then the fix is to rewrite the### Filteringexample and its note to
{current_user_id}, and the "only two tokens" claim stands.$currentUserreally does resolve in some filter path (a legacy alias somewhere inthe query layer) — then the Context Tokens section's "only two" is the false claim, and
under contract-first the alias is what should go, with the declaration made to match.
Route 2 would be an ADR-0087 conversion-layer question rather than a docs edit, which is why
this is filed rather than fixed in passing. Note the file is at its token ceiling: any fix
here is size-neutral at best.
Generated by Claude Code
Generated by Claude Code