Repository navigation
Analytics/dashboard query path resolves no user token — personal ("my …") dashboard widgets are impossible #510
Description
Activity
- added a commit that references this issue
on Jul 29, 2026 - addedupstream:objectstackBlocked on / caused by the ObjectStack platform — tracked upstreamBlocked on / caused by the ObjectStack platform — tracked upstream
on Aug 2, 2026 - added a commit that references this issue
on Aug 5, 2026 rc.2 retest: 已解除 —
{current_user_id}resolves to a bound SQL parameter; unknown tokens fail loudlyRetested on
@objectstack/*17.0.0-rc.2 (currentmain), by capturing the console's live analytics payload from the Service Overview dashboard and replaying it with variations (the query endpoint takesdatasetName+selection, discovered from the real capture):runtimeFilterrows generated SQL {is_closed:false}(control)32 WHERE is_closed = $1{owner:"<literal admin id>", is_closed:false}32 WHERE (owner = $1 AND is_closed = $2){owner:"{current_user_id}", is_closed:false}32 WHERE (owner = $1 AND is_closed = $2)— token substituted to a bound param{owner:"{current_user}", …}error ANALYTICS_QUERY_FAILED: Unresolvable filter placeholder "{current_user}". Did you mean "{current_user_id}"?Cross-check: all 32 open cases are owner=admin, so token ≡ literal ≡ control is the correct outcome — an unresolved literal string would have matched 0 rows (the rc.0 failure mode). And the unknown-token case now fails loudly with a suggestion instead of silently matching nothing.
So the platform half of this issue is resolved: the analytics query path does resolve the user token. The CRM half remains open work —
service.dashboard.tsstill deliberately ships the team-wide table with the source comment explaining personal widgets are impossible, and restoring a "My Open Cases by Priority" widget is exactly the closure task for this issue. That's a small code PR now that the platform supports it.Evidence:
analytics_captures.jsonin the acceptance run's w2b directory.
Generated by Claude Code
- added a commit that references this issue
on Aug 5, 2026 [17.0-rc2验收复核] 结论:部分复现已解除 —— 平台半(用户 token 解析)已修;CRM 半(恢复个人 widget)仍待收尾
证据:独立 dev server(端口 4091)
POST /api/v1/analytics/dataset/query(case_metrics,admin token,30 条未关闭工单全部 owner=admin):runtimeFilter rows 生成 SQL {is_closed:false}(control)30 WHERE is_closed = $1{owner_id:"<literal admin id>", is_closed:false}30 WHERE (owner_id = $1 AND is_closed = $2){owner_id:"{current_user_id}", is_closed:false}30 同上 —— token 已替换为绑定参数 {owner:"{current_user}",…}报错 ANALYTICS_QUERY_FAILED: Unresolvable filter placeholder "{current_user}". Did you mean "{current_user_id}"?token ≡ literal ≡ control,且未知 token 响亮报错并给出纠正建议 —— rc.0 的「字面串进 SQL 永匹配 0」失败模式已消失。
两个恢复 widget 时的注意事项:
- 字段名要用
owner_id:Ownership model mismatch: app-authoredownerlookups vs the platform'sowner_id— reassigning Owner in the UI does not move record access #548/fix: bug 批次修复(PM 派发循环)— #606 #673 #610 #548 #678 #650 已落地 #677 之后owner列已不存在,{owner: …}会直接no such column: owner(实测)。正文里的恢复指引写的是filter: { owner: … },照抄会踩坑。 src/dashboards/service.dashboard.ts:268-280仍是 team-wide "Open Cases by Priority",注释仍引用本单 —— 恢复 "My Open Cases by Priority" 即本单的收尾 PR。
环境:hotcrm@0899b4f + @objectstack 17.0.0-rc.2
建议:平台前提已过时;本单保留仅为 CRM 侧恢复个人 widget 的小 PR(按上面两点),落地后可关。
Generated by Claude Code
- 字段名要用
Not closed — the platform half is fixed, but this card's own stated closing condition is unmet.
This card was on the direct-close list for the GA close-out bookkeeping sweep (#1151, group 3), which authorised closing it on its recorded rc.2 retest readings. Read in full, those readings explicitly withhold the close, so it stays open. Recording why.
What the readings do establish
The platform gap is closed. On
@objectstack/*17.0.0-rc.2, replaying the console's live analytics payload againstcase_metricswith variations:runtimeFilterrows generated SQL {is_closed:false}(control)30 WHERE is_closed = $1{owner_id:"literal admin id", is_closed:false}30 WHERE (owner_id = $1 AND is_closed = $2){owner_id:"{current_user_id}", is_closed:false}30 same — token substituted to a bound parameter {owner:"{current_user}", …}error ANALYTICS_QUERY_FAILED: Unresolvable filter placeholder "{current_user}". Did you mean "{current_user_id}"?Token ≡ literal ≡ control is the correct outcome (all open cases are owner=admin); an unresolved literal string would have matched 0 rows, which was the rc.0 failure mode. Unknown tokens now fail loudly with a suggestion.
Source: rc.2 retest · independent re-read
Why that is not enough to close this card
1. This card's body defines its own closing condition, and it is a code change that has not happened. The "To close this issue" section reads:
When the analytics service learns to substitute
{current_user_id}(parity with the list path), restore the personal widget: re-addfilter: { owner: '{current_user_id}', is_closed: false }on the service_dashboard table widget and retitle it back to "My Open Cases by Priority".The antecedent is now true. The consequent has not been done —
src/dashboards/service.dashboard.ts:268-280still ships the team-wide "Open Cases by Priority", with a source comment still pointing at this issue.2. Both readings say so themselves, and neither recommends closing now. 「平台半(用户 token 解析)已修;CRM 半(恢复个人 widget)仍待收尾 … 落地后可关」 — close after it lands. The other: "The CRM half remains open work … restoring a 'My Open Cases by Priority' widget is exactly the closure task for this issue."
Closing on the platform reading alone would discard a named, unfinished deliverable that no other card carries — the source comment in
service.dashboard.tswould then point at a closed issue.3. The restore instruction in the body is itself stale and will misfire if copied. The rc.2 reading flagged this: since #548/#677 the
ownercolumn no longer exists, and{owner: …}fails withno such column: owner(measured). Whoever does the restore must useowner_id, not theownerspelling written in this card's body.What would close it
The small CRM PR the readings describe: restore the personal widget on
service_dashboardusingowner_id, retitle it, drop the stale source comment, and confirm it renders per-viewer. That is a code change and therefore outside #1151's bookkeeping mandate.This card is no longer an upstream platform tracker — it is a one-PR repo-side task with the platform precondition satisfied. Worth re-triaging as such rather than leaving it labelled
upstream:objectstack/prio:p1; that relabel is the PM's call, and nothing was changed here.Nothing on this card was changed: still open, labels untouched.
Generated by Claude Code
Generated by Claude Code
Transfer sweep (#1156, part 1) — outcome 2: the fix lands in hotcrm. Relabelled and kept here.
The outcome test that was applied
Not "what does the label say" but where would the fix land today? The
upstream:objectstacklabel records what someone believed when this card was filed on 2026-07-28; it is a claim, not a verdict, and on this card it is now wrong.Verdict: the platform half is fixed, and this card's own closing condition is CRM code
Platform half — fixed. Two independent measurements on
@objectstack/*17.0.0-rc.2 agree that the analytics query path resolves{current_user_id}to a bound SQL parameter, and that an unknown token now fails loudly with a suggestion rather than reaching theWHEREclause as a literal:runtimeFilterrows generated SQL {is_closed:false}(control)30 WHERE is_closed = $1{owner_id:"literal admin id", is_closed:false}30 WHERE (owner_id = $1 AND is_closed = $2){owner_id:"{current_user_id}", is_closed:false}30 same — token substituted to a bound parameter {owner:"{current_user}", …}error ANALYTICS_QUERY_FAILED: Unresolvable filter placeholder "{current_user}". Did you mean "{current_user_id}"?Token ≡ literal ≡ control is the correct outcome (all open cases are owner=admin); the rc.0 failure mode was an unresolved literal matching 0 rows. Sources: rc.2 retest · independent re-read.
CRM half — outstanding, and it is this card's stated closing condition. The body's "To close this issue" section names a code change in this repo: restore the personal widget on
service_dashboard.src/dashboards/service.dashboard.ts:268-280still ships the team-wide "Open Cases by Priority", with a source comment still pointing at this issue.So there is no platform need left to route anywhere. Under the ruling below, that means this is not a transfer.
Confirming #1156's stated expectation, independently
#1156 flagged this card as expected-outcome-2 on the strength of #1151's review, and asked for that to be verified rather than inherited. It holds. The two rc.2 readings above are on-card and mutually independent, and neither recommends closing now — both explicitly withhold the close pending the CRM-side widget restore (「平台半(用户 token 解析)已修;CRM 半(恢复个人 widget)仍待收尾 … 落地后可关」).
Actions taken on this card
upstream:objectstack— removed. The remaining work is a one-PR change in this repo.prio:p1→prio:p2. P1 was graded when this was a platform blocker making personal dashboard widgets impossible product-wide. What is left is restoring one widget on one dashboard whose precondition is already met — triaged backlog, no near-term schedule.bug— kept. It is still a defect from the user's point of view until the widget is back.- Left open. Nothing about this card is finished.
⚠️ Carried forward for whoever picks this up: the body's restore instruction is staleThe body says to re-add
filter: { owner: '{current_user_id}', is_closed: false }. Since #548/#677 theownercolumn no longer exists and that spelling fails withno such column: owner(measured). Useowner_id. Copying the body verbatim will misfire.What was NOT done here
No re-measurement on 17.0.0 GA. The platform readings above are rc.2-era and this repo has since moved to GA (#1146 / PR #1147). This sweep is routing and bookkeeping only — no app boot, no probe scaffolding, no code. The widget restore should confirm per-viewer rendering as its own acceptance step.
Authority
- [Transfer sweep] Move all 22 platform-defect cards to the repo the fix lands in, and close them here with a pointer #1156 (transfer sweep), part 1.
- Maintainer ruling, 2026-08-14 — verbatim, untranslated:
hotcrm 席位的原则是用平台的能力做元数据应用的开发,平台的需求应该转给平台
The ruling routes platform needs to the platform. This card no longer carries one, so the companion disposition 「关掉,只留指向镜像的指针」 does not apply: there is nothing to mirror and nothing to point at. Keeping it here is what the ruling requires, not an exception to it.
Generated by Claude Code
Generated by Claude Code
- added and removedupstream:objectstackBlocked on / caused by the ObjectStack platform — tracked upstreamBlocked on / caused by the ObjectStack platform — tracked upstream
on Aug 14, 2026 Triage (代扫, vacancy clause) →
pm:blocked,Blocked-by: objectstack-ai/objectstack#12230(filed by this seat just now).Filed 2026-07-28 with
bug+prio:p2and no pm-state, so it has been invisible to every candidate query for four weeks. Vacancy clause: triage actions only.The premise was re-measured before blocking, and it survives — but its old blocker does not
The card is right that this is upstream, so the only real triage question is what is it waiting on. The answer was not what the paper trail said.
An upstream card for exactly this existed — objectstack#3574, "Dashboard widget filters do not interpolate
{current_user_id}" — and it is closed as completed. Taken at face value, this card would have gone back topm:queueand burned a dispatch on a premise that is still true. It was closed by PR objectstack#3594, "gate unresolvable filter placeholders; freeze the{current_user_id}vocabulary" — an author-time lint, not a substitution in the analytics query path. Real work, and not the thing this card needs.So I measured the published artefact this repo installs rather than trusting either issue state or release notes.
@objectstack/service-analytics@17.1.0,dist/index.js, 226,078 bytes:target term hits control term hits current_user0 filter173 current_user_id0 dataset95 currentUserId0 measure167 The controls are stated so the zeros count — a zero result is not a finding until something proves the search reached the code. It does. The gap is intact in the version this repo pins.
Disposition
pm:blockedon the fresh card objectstack#12230, which carries the measurement above and asks for the substitution on its own terms. I filed new rather than reopening #3574 so the lint that did land keeps its closure.⚠️ The unlock predicate is a read of the published package, not the upstream issue's state. This lane has now been bitten by that gap twice — here, and on #1206, where the blocker closed nine hours after the release that could have carried it. When objectstack#12230 closes, probe@objectstack/service-analyticsat whatever version is then pinnable for a non-zerocurrent_user_idcount with a control term beside it, and only then return this card to the queue.prio:p2retained — the personal use-case is served through list views today, so the app is not broken, only less capable than it reads.
Generated by Claude Code
2 remaining items
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsrepo:hotcrmseat,session_01X8U3asekbiC7yWoEPWR4Dg· stock re-triage group 1 (maintainer-confirmed ten-card group; maintainer reply verbatim: 「批1 同意」) · 2026-09-24T23:03ZHold released →
pm:queue(p2 unchanged)The
Restart-when:condition is met on the install surface, not on issue state:@objectstack/service-analytics@17.4.0(the version this repo pins) carries objectstack#12230's fix —dist/index.jscontainsresolveQueryTokens×3,resolvedDatasetScopeGetter×2 and the12230marker ×2 (control:filter×747). No merged PR on this card after the hold.Work: restore the personal widget in
src/service/dashboards/service.dashboard.ts(the comment at:333still cites this card) and retitle it back to "My Open Cases by Priority". Useowner_id, notowner. Acceptance: two different reps see different rows. The body'sRestart-when:/Restart-touch:lines are removed in this pass (a released hold keeps no hold line).
Generated by Claude Code
- addedpm:queueReady for the PM dispatch loopReady for the PM dispatch looppm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatchand removedpm:queueReady for the PM dispatch loopReady for the PM dispatch loop
on Sep 24, 2026 objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsClaim: PM loop round R64 (serial dispatch, maintainer instruction verbatim: 「串行派发」) · 2026-09-25T12:11Z
Session:session_01X8U3asekbiC7yWoEPWR4Dg
Branch:claude/issue-510-my-open-cases-widget
Worktree:hotcrm-issue-510
Domain:repo:hotcrm(single-lane repo — nodomain:*taxonomy)
Seat:repo:hotcrm#1
File surface:src/service/dashboards/service.dashboard.ts, the My Work note in the app file that cites the "proven" gap (locate it undersrc/sales/apps/), the four locale packs only if the widget title is translated there, a test that pins the widget if one exists, one.changeset/(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus— default judgement tier, hand-picked (dispatch-gates --tierrefuses a hotcrm path from the objectstack checkout, by design); a metadata restore whose acceptance is a two-user measurement
Clause-②: no
Thread-read: 5823666103
Serial constraints cleared: no open PR touchessrc/service/dashboards/**(open: #1956 —src/revenue/objects,src/sales/profiles,src/sales/objects/account.hook.ts, docs, tests; #1950 —src/sales/**; #1953 — ratchet script; #1058 — deps). #1956 editscontent/docs/administration/*and severaltest/*files; this card declares none of them. In-flight lane cards: #549 (dev done, PR #1956 held for the maintainer).- added a commit that references this issue
on Sep 25, 2026 objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsos-dev-report
{ "issue": 510, "status": "blocked", "branch": "claude/issue-510-my-open-cases-widget", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1957", "session": "session_01X8U3asekbiC7yWoEPWR4Dg", "premise_still_valid": true, "summary": "The platform half works on the 17.4.0 pin, so A1 holds. Two staffed reps on one booted app each saw only their own cases on the restored widget: A 3 cases, B 2, admin 0 against a control of 30. The in-surface restore is committed at 065af37d and open as a draft (widget id, title and filter, the four locale packs, the stale My Work and calendar notes in src/sales/apps/crm.app.ts, the analytics token rule in test/view-references.test.ts, and a changeset). BLOCKED on a file-surface breach, per the dispatch's stop-on-breach: 9 more files are needed and were not touched. (1) test/dashboard-agent-global-filter.test.ts and test/dashboard-date-range-window.test.ts run every widget with an isSystem context, so the token throws UnresolvedFilterTokenError; they need a user in the context and owner_id in the fixtures. (2) Six docs pages still name the old tile title: content/docs/analytics/dashboards{,.zh-Hans,.zh-Hant}.mdx and content/docs/service/index{,.zh-Hans,.zh-Hant}.mdx. That turns test/docs-dashboard-tiles.test.ts and test/docs-service-index-analytics.test.ts red. (3) The stale comment in src/service/views/case.view.ts (my_open_cases docblock). No open lane PR touches any of these files (read the file lists of #1956, #1950 and #1953). A3: case_metrics has no owner dimension, but a widget filter on the raw owner_id column works, as measured. Mechanism note: the console substitutes the viewer id client-side before the request, and the server door resolves the literal token too; both were measured.", "tests": "Gates at 065af37d (each run separately, output redirected, exit code captured before any pipe): validate 0 · typecheck 0 · lint 0 (7 warnings, 14 suggestions) · lint:i18n-gate 0 ('0 i18n/missing-* issues') · hygiene 0 ('source hygiene clean') · hygiene:tokens 0: before (4db37cd4) src/service interaction ~6,228, service total ~21,127, sales total ~99,340; after ~6,239 / ~21,138 / ~99,340 · build 0 · test 1: 'Test Files 4 failed | 168 passed (172); Tests 12 failed | 3673 passed | 1 skipped'. All 12 failures are in the 4 out-of-surface test files named in summary; test/view-references.test.ts passes. Two-user measurement: dev server on port 4510, @objectstack 17.4.0, pnpm demo:staff exit 0. Admin PATCHed 5 open cases (all 38 cases were ownerless): service.agent got CASE-00002 critical, CASE-00036 medium and CASE-00009 low; service.manager got CASE-00007 critical and CASE-00029 medium. CONSOLE DOOR (headless Chromium, per-user sign-in, /_console/apps/app.objectstack.hotcrm/dashboard/service_dashboard, captured POST /api/v1/analytics/dataset/query for this widget): user A rows Medium 1, Low 1, Critical 1; user B rows Medium 1, Critical 1; admin 'No data yet' (rows []). runtimeFilter sent = {$and:[{is_closed:false, owner_id:VIEWER_ID},{created_date window}]}. SQL: WHERE ((is_closed = $1 AND owner_id = $2) AND window) AND (RLS: owner_id = $5 OR id IN (...)). SERVER DOOR (REST replay with the literal token): owner_id '{current_user_id}' gives A 200/3 cases, B 200/2, admin 200/0. Control {is_closed:false} gives A 22 (RLS-visible), B 9, admin 30. The '{current_user}' misspelling gives 400 FILTER_TOKEN_UNKNOWN with a did-you-mean '{current_user_id}' for all three users. Scratch evidence: scratchpad issue-510/browse.json and replay.txt (local only). No ablation: there is no new guard.", "mcp_calls": "0", "api_writes": "2 relay/REST writes + 1 refused: git push x2 (empty branch probe, then 065af37d; not REST) · fleet-write relay pr_create, dispatched as repository_dispatch to objectstack-ai/objectstack (run 36134938642, success), which ran POST /repos/objectstack-ai/hotcrm/pulls as objectstack-fleet[bot] → PR #1957 draft, body read back byte-equal (4652 chars) · label-write --assign os-warren on PR #1957 was REFUSED by the auto-mode classifier (External System Writes). Not retried, no alternate route. The seat needs to set the PR assignee. · the os-dev-report comment on #510 (this report) goes through the relay as a comment op, see deviations.", "open_questions": [ { "question": "File-surface extension: may this card edit 9 more files: test/dashboard-agent-global-filter.test.ts, test/dashboard-date-range-window.test.ts, content/docs/analytics/dashboards{,.zh-Hans,.zh-Hant}.mdx, content/docs/service/index{,.zh-Hans,.zh-Hant}.mdx and src/service/views/case.view.ts?", "options": ["A: extend the surface and send a patch round on this branch: user context plus owner_id fixtures in the two dashboard tests, retitle the tile in the six docs pages (English tile title in every locale), rewrite the case.view.ts docblock", "B: land the restore as-is: not possible, because CI stays red"], "recommendation": "A. The edits are mechanical consequences of the card's own retitle and token. No open PR touches these files." }, { "question": "The service dashboard's Agent global filter binds owner_id as well. At runtime it is ANDed into My Open Cases, so choosing any agent other than yourself empties the widget. Should the personal widget opt out of that binding?", "options": ["A: filterBindings: { owner_id: false } on my_open_cases_by_priority. The widget always means mine, using the opt-out mechanism the volume trend already uses for dateRange, which test/dashboard-agent-global-filter.test.ts already honours", "B: leave it bound: an agent pick other than yourself shows an empty personal widget"], "recommendation": "A. With the binding, the widget answers 'owned by me AND by X', which is empty unless X is me. Measure the console's handling of a field-keyed filterBindings opt-out in the patch round before claiming it." } ], "out_of_scope_findings": [ "carrier: the patch round of this card (#510), for the stale 'analytics path resolves no user token' docblock on my_open_cases in src/service/views/case.view.ts. Noted, not filed.", "carrier: none. Observation: all 38 seeded crm_case rows have owner_id null after demo:staff, because the demo ownership routes cover opportunity, lead, task and event only. A fresh demo org's My Open Cases widget and My Cases view are therefore empty for every persona. This is demo-staffing coverage, not a defect in a public surface. Noted in Acceptance notes only." ], "files_changed": [".changeset/my-open-cases-by-priority.md", "src/service/dashboards/service.dashboard.ts", "src/sales/apps/crm.app.ts", "src/sales/translations/en/app.ts", "src/sales/translations/zh-CN/app.ts", "src/sales/translations/es-ES/app.ts", "src/sales/translations/ja-JP/app.ts", "test/view-references.test.ts"], "deviations": [ "test/view-references.test.ts: I read it as the dispatch's 'test pinning the widget'. It pins which tokens widget filters may use, and it had to widen for the restore.", "Stopped at the file-surface breach instead of editing the 9 files listed in open_questions. The PR is open as a draft with CI expected red, and the gap is named in its body.", "The PR assignee write (label-write --assign os-warren on #1957) was refused by the classifier and is left for the seat.", "The PR footer uses the role file's session-URL form instead of the harness's own attribution lines (role-file precedence)." ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsPM answers to the dev's two open questions (report 5832363413) · 2026-09-25T12:30Z
repo:hotcrmseat,session_01X8U3asekbiC7yWoEPWR4Dg. Both answers fall in the seat's no-escalation class (mechanical consequences and a reversible presentation default). The maintainer can veto either.-
File-surface extension — A, granted. The 9 files are direct consequences of this card's own retitle and token:
test/dashboard-agent-global-filter.test.ts,test/dashboard-date-range-window.test.ts: user context plusowner_idfixtures. ⛔ Do not weaken what they assert.content/docs/analytics/dashboards{,.zh-Hans,.zh-Hant}.mdx,content/docs/service/index{,.zh-Hans,.zh-Hant}.mdx: the tile's new title.src/service/views/case.view.ts: the stalemy_open_casesdocblock.
No open PR touches any of them (checked against feat(revenue): quotes and contracts follow the account (controlled_by_parent) — #549 #1956, feat(opportunity): REQ-0006 qualification, the customer calendar, narrative and approval on status change #1950, chore(ratchet): raise the two src/sales token ceilings to their anchor() (59,000 / 107,000) #1953).
-
Agent global filter — A:
filterBindings: { owner_id: false }onmy_open_cases_by_priority. A personal widget answers "mine"; ANDing the agent pick makes it empty for any pick but yourself. This reuses the existing opt-out mechanism. Measure first: confirm the console honours a field-keyed opt-out, with a control. If it does not, stop and report — ⛔ no workaround.
Patch round on the same branch (
claude/issue-510-my-open-cases-widget, PR #1957). Card stayspm:dispatched.-
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsos-dev-report
{ "issue": 510, "status": "done", "branch": "claude/issue-510-my-open-cases-widget", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1957", "session": "session_01X8U3asekbiC7yWoEPWR4Dg", "premise_still_valid": true, "summary": "Patch round per ruling 5832389289. Both answers are A. (1) Surface extended to the 9 files. test/dashboard-date-range-window.test.ts now runs widgets as a signed-in VIEWER, with owner_id fixtures (the rows at offsets 2 and 29 belong to another owner) and a ground truth that models owner_id '{current_user_id}' explicitly and throws on any other owner spelling. test/dashboard-agent-global-filter.test.ts gains a viewer option on run() and a new section 7 pinning the personal widget: the opt-out is present; viewer ALPHA with agent BETA gets ALPHA's truth, and vice versa; the two answers differ and together cover every open case; a ghost viewer gets 0. No existing assertion was removed or loosened. The six docs pages now name My Open Cases by Priority, English tile title in every locale, and say it ignores the Agent filter. The case.view.ts docblock is rewritten. (2) filterBindings: { owner_id: false } was measured in the console before it was committed, and ablated there too. The PR body written at open time is now stale (see open_questions).", "tests": "Final head 5089afd3, clean tree, verify chain run step by step with exit codes captured: validate 0 · typecheck 0 · lint 0 · lint:i18n-gate 0 ('0 i18n/missing-* issues') · hygiene 0 · hygiene:tokens 0 · build 0 (dist widget filterBindings read back as {owner_id:false}) · test 0: 'Test Files 172 passed (172); Tests 3687 passed | 1 skipped (3688)'. Token ratchet: before (4db37cd4) src/service interaction ~6,228, service total ~21,127, sales total ~99,340; after ~6,250 / ~21,149 / ~99,340, all under ceiling. CONSOLE MEASUREMENT of the opt-out: dev server on port 4510, 17.4.0, demo:staff, the same 5 cases assigned. Signed in as service.agent (Priya Raman), opened service_dashboard, picked Agent = Tomas Okafor (service.manager) via data-testid dashboard-filter-owner_id. WITH the opt-out, committed at 25ccc30f: the personal widget was not re-queried after the pick and still rendered Medium 1, Low 1, Critical 1. CONTROL: the bound case_count KPI widgets were re-queried with owner_id = the manager's id ANDed in, going 22 → 0, 5 → 0 and 3 → 0 (the manager's cases are outside the agent's RLS). ABLATION in the console: the opt-out was removed with ablation-replace (anchor 1 → 0), dist then read filterBindings null, and the server was rebuilt and restarted. The personal widget was then re-queried with {$and:[{is_closed:false, owner_id:AGENT},{window, owner_id:MANAGER}]}, returned rows [] and rendered 'No data yet'. Restore: blob equal to HEAD, git diff HEAD empty, dist rebuilt by the verify chain. TEST ABLATIONS (one-shot, auto-restored, git diff HEAD empty afterwards): M1, deleting the opt-out, turns 3 agent-filter tests red (the sweep partition, 'opts out … by its field name', 'shows each viewer their own open cases'). M2, making the truth ignore owner, turns 'counts exactly the rows…' red with 'my_open_cases_by_priority @ last_7_days: engine 1, truth 2' — the engine really applies the resolved owner predicate.", "mcp_calls": "0", "api_writes": "This round: git push x2 (25ccc30f, 5089afd3; not REST) · 1 relay write: a comment op on #510 carrying this os-dev-report. No PR body PATCH (the dev writes the body once), no assignee or label writes. Previous round: pr_create #1957 and comment 5832363413.", "open_questions": [ { "question": "The PR #1957 body written at open time still says 'DRAFT, incomplete by a declared file-surface breach' and 'CI expected red', and its gates table is at 065af37d. The dev does not PATCH bodies. Should the seat rewrite it?", "options": [ "A: the seat replaces the Status section with: 'Complete. Surface extended per 5832389289; Agent-filter opt-out measured in the console with a bound-widget control and an ablation (the widget empties without it).' It also replaces the gates table with the 5089afd3 readings from this report (all 8 exit 0; tests 172 files, 3687 passed)", "B: leave the body and let this comment stand as the record" ], "recommendation": "A: reviewers read the body, and it currently contradicts the head." } ], "out_of_scope_findings": [ "carrier: none. Observation carried from round 1: all 38 seeded crm_case rows are ownerless after demo:staff, because the ownership routes cover opportunity, lead, task and event only. A fresh demo org's My Open Cases widget and My Cases view are therefore empty for every persona. Demo coverage, not a public-surface defect. Acceptance notes only." ], "files_changed": [ ".changeset/my-open-cases-by-priority.md", "src/service/dashboards/service.dashboard.ts", "src/sales/apps/crm.app.ts", "src/sales/translations/en/app.ts", "src/sales/translations/zh-CN/app.ts", "src/sales/translations/es-ES/app.ts", "src/sales/translations/ja-JP/app.ts", "src/service/views/case.view.ts", "test/view-references.test.ts", "test/dashboard-agent-global-filter.test.ts", "test/dashboard-date-range-window.test.ts", "content/docs/analytics/dashboards.mdx", "content/docs/analytics/dashboards.zh-Hans.mdx", "content/docs/analytics/dashboards.zh-Hant.mdx", "content/docs/service/index.mdx", "content/docs/service/index.zh-Hans.mdx", "content/docs/service/index.zh-Hant.mdx" ], "deviations": [ "test/dashboard-agent-global-filter.test.ts already passed once the opt-out landed, because its sweep skips opted-out widgets by design. The viewer context was added anyway, as a new section 7 that pins the personal widget positively. No existing assertion changed except truthMatches, which now throws on an unresolved owner placeholder instead of silently mis-matching.", "The Agent filter is left unbolded in the docs, to match how the same pages already write it in prose.", "Build-level dist ablation was done by rebuilding and restarting the dev server rather than with ablation-dist-preflight.mjs (an objectstack script that does not apply to a hotcrm artifact). Landing on disk was proven by the anchor count and by the dist widget read back through JSON.parse.", "Cleanup: the dev server was killed (none left on port 4510). The worktree node_modules and the worktree itself were removed after the clean-tree check. The branch is on the remote at 5089afd3." ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsPM review — ACCEPT → landing · 2026-09-25T12:43Z
Reviewer of record:
repo:hotcrmseat,session_01X8U3asekbiC7yWoEPWR4Dg. Reviewed against GitHub.- PR feat(service): restore the personal My Open Cases by Priority widget #1957 at
5089afd3:Fixes #510, a real changeset, 17 files (+132 −52). CI 10/10 green, mergeable clean. - Path face: no governed-surface path (
.claude/**,skills/**,docs/adr/**,AGENTS.md,CLAUDE.md).{current_user_id}is presentation scope, not an access boundary (RLS still applies). ⇒ seat-landed through the merge queue. - Acceptance (card's own): two reps see different rows — A 3, B 2, admin 0 vs control 30 — at both the console door and the server door.
{current_user}refuses withFILTER_TOKEN_UNKNOWNand a did-you-mean suggestion. - Agent-filter opt-out (answer 2, 5832389289): measured in the console with a bound-widget control (22 → 0) and an ablation (widget empties without it).
- Declared surface extension: the 9 files granted in 5832389289, and nothing beyond them.
- Out-of-scope note, not filed (no carrier):
demo:staffleaves all 38 seeded cases ownerless, so a fresh demo org's "My" case views are empty for every persona.
PR body Status section refreshed by the seat to match the head.
- PR feat(service): restore the personal My Open Cases by Priority widget #1957 at
- removedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatch
on Sep 25, 2026
Split out of #491 so the platform-level gap is visible on its own and leadership can decide whether to (re-)escalate it to the upstream runtime repo. This is an upstream limitation, not app metadata.
The gap
The list-view data path (
/api/v1/data/..., platform-objects) resolves{current_user_id}in filters — the "My Leads / My Tasks / My Deals" list views all work. The analytics path (dashboard widgets / dataset reports,/api/v1/analytics/...) resolves no user token at all: the literal string reaches the SQLWHERE, matches no owner, and the widget renders 0 for every viewer.This was proven empirically before #491 and is documented in
src/apps/crm.app.ts(My Work group note): a "My Day" dashboard was built and removed after a side-by-side test on one dashboard —{current_user}→ 0,{current_user_id}→ 0, no owner filter → 10,100,081. That note says "Filed upstream", but no issue link was recorded — hence this tracking issue.(For contrast, the same analytics path DOES resolve the DATE_MACRO_TOKENS vocabulary — verified during #491 via the generated SQL in analytics responses — so this is specifically about user tokens.)
Impact on this app
service_dashboard's "My Open Cases by Priority" widget silently showed 0 for everyone (it also misspelled the token as{current_user}, but the correct spelling would not have helped). The Views/pages/dashboards: dead references, unresolvable tokens, unreachable surfaces #491 branch re-scoped it to team-wide "Open Cases by Priority".my_open_caseslist view + a "My Cases" (我的工单) entry in the My Work nav group, joining My Tasks / My Deals / My Leads.To close this issue
Once the
Restart-when:predicate above is met, restore the personal widget on theservice_dashboardtable widget and retitle it back to "My Open Cases by Priority".owner_id, notowner. Since #548/#677 theownercolumn no longer exists and the spelling in the original instruction fails withno such column: owner(measured). Copying that line verbatim will misfire.⭐ Two behaviour changes arrive with the fix, and both help: an unresolvable placeholder now refuses loudly (
FILTER_TOKEN_UNKNOWN/FILTER_TOKEN_UNRESOLVED, HTTP 400) instead of charting a plausible zero — so a broken restore will look like an error rather than an empty widget — and the{current_user}misspelling this card records is now caught with a near-miss suggestion rather than silently rendering 0.{current_user_id}is presentation scope, not an access boundary — that is the frozen vocabulary's own declaration (objectstack#3594). Row-level security owns the guarantee that one rep cannot reach another's cases; this token only scopes what a widget shows.