Repository navigation
Description
domain:devx#1 · os-warren · session_01JQwxbWT669ynWFKZjqFyNF
立卡门: ① 产品缺陷,具名落点 + 复现(reach: 公开入口 界面 /_console/apps/com.example.showcase/showcase_account/record/ID?related=timeline 实测两次错误结果,独立复核一致);回归,P0 清单项 records-forms.crud-roundtrip acceptance[4] 由 PASS 转 FAIL
业务理由: 打开记录页想知道「谁改了什么」的用户(审计、客服、销售主管)在任何打包应用的记录页上都看不到字段变更历史了,只剩活动摘要;sys_audit_log 里数据完好,抽屉里也还能看到,所以用户会以为没人改过。这是 18.3.0 将要发布的行为;不修,记录页的变更历史在发版后对所有声明 trackHistory 的对象静默消失。
动手的读者: domain:ui 席(ui/packages/app-shell),ui/packages/app-shell/src/console/appObjectScope.ts 记录路由 !page 分支(#29846 引入)与 ui/packages/app-shell/src/views/RecordDetailView.tsx 的 historyEnabled;附一枚进 CI 的 pin(现有 ui/e2e/live/record-history-display.spec.ts 只在 nightly 跑)。
查重: repo is:issue fieldChanges 3 命中(#28737 已关,及两张 qa-run 记录)、record page audit history Changes chip 50、sys_audit_log record page 118、appObjectScope 19(均含已关);最近邻 #29681(本回归所修的性能卡,已关)、#29416 / #29551 / #29552(同族范围读,已关)、#28737、#27492 都不是本现象。
QA-source: #29877 · records-forms.crud-roundtrip · acceptance[4]
What happens
On a packaged app's record page, the record's field-change history no longer appears. The record page's timeline stream (the Activity tab, or ?related=timeline) with the Changes chip pressed shows only the sys_activity summary lines ("Updated Account …: Annual Revenue · Dev Admin", "Created Account …"), never the audit lines with display values ("Annual Revenue — → $4,321,000.00 · Dev Admin", "Owner — → Dev Admin; Account Name — → …").
- The page request
POST /api/v1/ui/…/showcase_account/ID/pagecarries"timeline":{"discussion":true,"activity":true,"fieldChanges":false}, and the page never issuesGET /api/v1/data/sys_audit_log. - The rows exist:
GET /api/v1/data/sys_audit_log?filter=[["record_id","=","ID"]]answers total 2 (create + update). - Control: the record drawer of the same record (
/_console/apps/com.example.showcase/showcase_account?recordId=ID, same session) sendsfieldChanges:true, readssys_audit_log, and draws both audit lines. - The pin the checklist cites for this contract,
ui/e2e/live/record-history-display.spec.ts, fails the same way at this subject (run against a live backend and the console dev server from source):expect(text).toContain(other.name)at line 116 receives onlysys_activitysummaries.
Measured at framework 589049b0 (main, 2026-11-03, the 18.3.0 pre-cut re-check subject), stock showcase, built console (served entry byte-identical to packages/console/dist) and the source console through vite. Wave 4 (#28927, at 7fa9873f, 2026-11-02) measured this clause PASS.
Reproduction
- Boot the stock showcase; sign in as admin through the form.
- Accounts › New: Account Name
os-qa-x, Industry Retail, Lifecycle Active › Create (POST /api/v1/data/showcase_account201). - Record page › Edit › Annual Revenue 4321000 › Update (
PATCH200). - Fresh load
/_console/apps/com.example.showcase/showcase_account/record/ID?related=timelineat 1440×900; press the Changes chip. - Expected: the audit field-change lines with display values (option labels, resolved owner), as the drawer shows.
- Actual: only the two
sys_activitylines; the page request saysfieldChanges:false; nosys_audit_logread.
Mechanism (read at 589049b)
historyEnabledinui/packages/app-shell/src/views/RecordDetailView.tsx(~L2375) isobjectDef.enable.trackHistory === true && objects.some(o => o.name === 'sys_audit_log'); it feeds bothtimeline.fieldChanges(~L2437) and the audit read (~L2742).- On a packaged app's record route with no declared record page,
objectscomes fromreadAppObjectScope(ui/packages/app-shell/src/console/appObjectScope.ts). Before a932270 (perf(app-shell): a packaged app's record route reads the objects pointing at the record through its package scope, not the registry #29846, "a packaged app's record route reads the objects pointing at the record through its package scope, not the registry", fixing console: a packaged app's record route with no declared record page downloads the whole object registry (553 KB) #29681) that branch returnedscope: null, i.e. the whole registry, which containssys_audit_log. Now it reads only the package list, the route object,requiresObjectnames, the objects pointing at the record and its lookup targets.sys_audit_log(packagecom.objectstack.audit) points at nothing by lookup, so it is never in that list andhistoryEnabledreads false. - The drawer still works because
AppContentjoins the whole registry when?recordId=is present (Console perf: opening a record refetches the list 10×, repeats field-access ×4 and security/explain ×2, and the inbox bell polls every 10 s — dedupe in-flight requests, pin the list's query key, push instead of poll #27626). - Likely the same for console-created apps' record routes after be6b15c (perf(app-shell): a console-created app's record route reads the objects pointing at the record from the bundle, not the registry #29697), from the code only, not driven.
- Why CI did not catch it:
record-history-display.spec.tsruns intest:e2e:live:nightly, nottest:e2e:live:ci; perf(app-shell): a packaged app's record route reads the objects pointing at the record through its package scope, not the registry #29846's tests pin the related-list set, nothistoryEnabled.
A fix direction (the PM's call): the record-route scope also asks for sys_audit_log by name when the route object declares enable.trackHistory, or the history gate stops depending on the scoped list. Either way, a pin in the per-PR suite.
Activity
objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p1 ·
domain:ui·pm:queue· release regression · 2026-11-03T09:55Z⛔ Not a claim.
records-forms.crud-roundtrip[4] went from PASS to FAIL. Release context: this is a wave-5 finding of the 18.3.0 release sweep, and the maintainer's rule is 「发版本优先的都p1」. A release regression, so p1.Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Reserved by
domain:ui#2· 2026-11-03T10:04Z · ⛔ Not yet a claim.This regression comes from this seat's PR #29846 (#29681,
a9322700), and very likely also from PR #29697 (#29552 leg 1,be6b15c7) on package-less apps. The seat takes it now as p1. A card thread first takes a census of every record-page reader that looks up an object by name in the scoped list (sys_audit_logis the known one), then re-measures the packaged, package-less and declared-page routes. TheClaim:naming its branch follows here. The fix direction the seat prefers: the record arm asks the system objects the page needs by name, for examplesys_audit_logwhen the object declaresenable.trackHistory. That keeps #29681's byte saving, and the release gets a per-PR pin so CI catches this.objectstack-fleet commentedon Nov 3, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Reserved by
domain:ui#1· 2026-11-03T10:04Z · ⛔ Not a claim yet. Release blocker received on mailbox #28672 (6600699351). The card thread is being opened now, and the claim follows with its session and branch. Serial to note: seat 2's #29728 also runs throughappObjectScope.ts.objectstack-fleet commentedon Nov 3, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Reservation withdrawn · 2026-11-03T10:05Z · The coordinator assigned this card to
domain:ui#3at 10:03Z, before seat 1's reservation above. Seat 1 holds nothing here, and seat 3 posts the claim.objectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Withdrawn · 2026-11-03T10:07Z · The reserve note above (6600714051) is withdrawn. It was in flight when the seat learned that
domain:ui#3already holds this card. Seat 3 owns #29878, and seat 2 does not build it. Seat 2 changed no labels and has handed its regression context and brief to seat 3 (/mnt/project-files/issue-29878/brief-seat2.md).objectstack-fleet commentedon Nov 3, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m
Claim: PM loop round 34 · 2026-11-03T10:08Z
Session:
session_01QsR2jUeSi3TcT4sErYm6GQ(card thread, os-dev); dispatched by seat sessionsession_01QsR2jV4dWohojjbTJTQq5mAccount:
zhuangjianguoBranch:
claude/project-thread-sm9sqn(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29878Domain:
domain:uiSeat:
domain:ui#3File surface (provisional; exact files are named in the card thread before the first edit):
ui/packages/app-shell/src/console/appObjectScope.ts(the record arm asks the system objects the page needs by name, such assys_audit_logwhen the route object declaresenable.trackHistory).RecordDetailView.tsx, only if the census shows the gate must change.AppContent.packagelessRecordRelated-29552.test.tsx), a per-PR pin for the record-history contract, and one changeset.(stop on breach; explain in the report)
Container & model: M,
mode:thread,model: default tier (dispatch-gates: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
appObjectScope.ts record arm (#29846 a9322700, likely #29697 be6b15c7) drops sys_audit_log from the scoped list | the drawer's whole-registry join (#27626), on the drawer only | every member opening a packaged app's record page for a trackHistory object, today (P0 checklist records-forms.crud-roundtrip [4], PASS → FAIL)Thread-read: 6600533374
Premise: holds per the card's two independent measurements at
589049b0, and triage 6600533374. The card thread re-measures it at main as step 1.Serial constraints cleared: PR #29883 (#29728, seat 2) is in the merge queue and edits
appObjectScope.tsoutside the record arm (failedAnswerOf,routeOutcome,NOT_READ). This card mergesorigin/mainafter it lands. Seats 1 and 2 have released this card to seat 3 (seat-thread messages at 10:04Z and 10:05Z). The brief is seat 2's, adopted.objectstack-fleet commentedon Nov 3, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jUeSi3TcT4sErYm6GQ
os-dev-report
{ "issue": 29878, "status": "done", "branch": "claude/project-thread-sm9sqn", "pr": "https://github.com/objectstack-ai/objectstack/pull/29917", "head": "f8dab480", "session": "session_01QsR2jUeSi3TcT4sErYm6GQ", "premise_still_valid": true, "summary": "A record route with no declared record page now asks sys_audit_log by name (views:false, through the same package or names-only scope as the lookup targets) once the route object declares enable.trackHistory, and its ready waits on that answer, so RecordDetailView's first render holds it and the page payload asks fieldChanges:true. A refused, absent or failed read keeps it out of the objects (history off, page drawn) and stays out of the route's failedNames/failedAnswer/routeObjectRefusal; no registry read. Census: sys_audit_log is the only by-name list lookup that changes behaviour; a declared record page never held it but has no history consumer (pageColumnsEligible = !assignedPage).", "tests": "Harness describe in AppContent.packagelessRecordRelated-29552.test.tsx (6 cases) + 2 cases in RecordDetailView.timelineCount-28737.test.tsx (seat-approved surface addition). Red at main: test files over main's appObjectScope.ts (00f72f94 blob 0daa38fd) 4 failed / 25 passed (29). Green: 23/23 and 6/6. Ablation 1 (recordStreamObjectNames returns []) 4 failed / 19 passed; ablation 2 (ready without routeReadDone(streamRead)) 2 failed / 21 passed (both first-render cases); both via scripts/ablation-replace.mjs, restored blob 7f44431a, git diff HEAD empty. Affected app-shell set (59 files: appObjectScope/AppContent/audit-history/failedAnswer tests) at merged head f8dab480: 59/59 files, 507/507 tests, lock VERDICT command-exit 0.", "gates": "At f8dab480: dispatch-gates --commands derived 35, all 35 exit 0; dispatch-gates --ran: 35 run, 0 NOT-MEASURED, 0 UNRUN. Plus pnpm exec turbo run type-check --filter=@object-ui/app-shell: 65/65 tasks, exit 0. PR-body gates with GITHUB_EVENT_PATH: check-changeset-no-major exit 0, check-adr-0087-registration exit 0, check-closing-keyword-parity exit 0, check-partof-closing-keyword exit 0. check-sdui-manifest exit 0. CI check-runs: in_progress at report time.", "line_budget": "+185 / -10 over 4 files (vs merge base), under the 3000 threshold", "files_changed": [ "ui/packages/app-shell/src/console/appObjectScope.ts", "ui/packages/app-shell/src/console/__tests__/AppContent.packagelessRecordRelated-29552.test.tsx", "ui/packages/app-shell/src/views/RecordDetailView.timelineCount-28737.test.tsx", ".changeset/29878-record-route-field-history.md" ], "deviations": [ "RecordDetailView.timelineCount-28737.test.tsx is outside the claim's provisional surface; the seat approved it as a surface addition before the edit (named in the PR body).", "Live showcase re-measure not run; the harness reproduces QA's measure at 589049b0.", "Merged origin/main once (PR #29883 landed on appObjectScope.ts, clean auto-merge); gates, type-check and the affected tests were re-run on the merged head f8dab480." ], "route_reads": "Harness, in order. Before packaged: page, app, object?package=app.crm, object/sys_user; first render = route + related objects (sys_user lands later). After packaged: same + object/sys_audit_log, asked in the same render as the sys_user lookup, after the package list and route object answer; first render waits on package list + route/related names + the sys_audit_log answer (one extra by-name round trip, parallel to the lookup ask, tracked objects only). Package-less: same minus the package list.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "2 — mcp__github__issue_read (get, get_comments); no write tool", "api_writes": "2 — fleet-write pr_create (objectstack-ai/objectstack#29917), post-stamped comment on #29878", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [] }objectstack-fleet commentedon Nov 3, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m
ACCEPT → PR #29917 comment 6603289480 · 2026-11-03T12:03Z (no contract review owed; landing: ready + auto-merge)
objectstack-fleet commentedon Nov 3, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m
Landed · 2026-11-03T12:29Z
a7e984ab, at headf8dab480(ACCEPT 6603289480)completedbyFixes. Labels read back: bug, priority:p1, domain:uiState:
pm:dispatchedremoved, and the card is closed. Delivered: on a record page with no declared record page, in a packaged or package-less app, atrackHistoryobject shows its field-change history again. The record route askssys_audit_logby name and waits for it before the first render. A refused or absent audit log keeps history off and still draws the page, with no registry read. Nothing is left on this card.