Skip to content

Commit edf9714

Browse files
docs(qa): wave-3 accuracy slice S5 — approvals, ux-conventions, i18n, dashboards, search (#27377) (#28372)
<!-- claude-projects-attribution: {"github_login":"os-zhuang"} --> _Requested by **Sam** · [project thread](https://claude.ai/code/project/chan_01LjZwNRFiG2fkp8SNf6rHNx?thread=cmsg_01LjZwNRFiG2fkp8SNf6rHNxYKHWj4wrpfXwzrjAJepPr4)_ Fixes #28339 Part of #27377 (wave-3 checklist accuracy, slice S5) domain:devx#1 · os-zhuang · session_01QUs3BvP5Aq9USg5GtmgAAR (dispatching seat) · dev session_01QUs3BrWDv9TR18p6R88RwQ Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:校准五份 QA 测试清单 JSON(approvals、ux-conventions、i18n、dashboards、search)中 16 个条目的文字,对应 census 的 21 行;每条 revision +1,保留 main 的 history 前缀,只追加一行,引用 QA 运行记录(#27409 / #27441 / #27438)与 #27377。 - 为什么改:wave-3 的运行实测发现这些条目的步骤、夹具或判据说法已过时,例如审批记录页的「Admin override」折叠菜单、锁定时 Edit 是禁用而非隐藏、提醒的 notified 按去重地址计数、`$searchFields` 空值现在直接 400。 - 风险与回滚:只改 `docs/qa/` 下五份清单,不动产品代码、不发布;没有任何条款被放宽,新增两处覆盖(i18n 的 locale 形状变体、search 的空覆盖条款)。回滚即 revert 本 PR。 - 先看:`search.json` 的 `field-scoped-narrowing` 新增的 acceptance[4](census 原读法已被 #27484/#27683 改变,条款按今天的拒绝写);`approvals.json` 的 `request-page-reply-and-reader-visibility` acceptance[2](404 只约束被授予读权限的调用者,无授权者 403 为另一条腿);`approvals.json` 的 `record-page-decisions` acceptance[3]、[4]。 - 两分钟验证:`pnpm check:platform-checklist`,预期 `OK — 16 areas, 352 items`,symbol anchors `1073/1083`(新增 1 个锚点,可解析)。 - 需要你做的:无,席位落地。 ## What changes Five files, 16 items, covering the 21 census rows of slice S5 (census note 6539602007 on #27377, taken at `501c864dd5`). None of the five files changed between that base and this PR's base `22ce3d13`, so the census readings applied unchanged. Each row was then re-read against the source at `22ce3d13` before any edit. Each edited item's `revision` is +1 from main, main's `history` is kept as an exact prefix, and exactly one row is appended. That row cites the run record the finding came from in `ref` and the wave anchor #27377 in `change`. All 21 rows still described something stale at this base, so none was dropped as fixed upstream. One row's finding had moved, though: rows 162 and 170 (`search.field-scoped-narrowing`). The finding read that an empty `$searchFields` is treated as absent. At this base the doors refuse it (#27484, #27683). The new clause states today's refusal, not the finding's reading. No product defect was found, and no clause was weakened. | item | census row(s) | what changed (brief) | record | |---|---|---|---| | approvals.email-action-token-door | 51 | steps[1]: `notified` counts one reminder per distinct concrete address (approval-service.ts `remind`, a Set over `pending_approvers`; pin `remind-notified-per-person.integration.test.ts`), not one per slot | #27409 | | approvals.status-mirror-field | 52 | fixtures.requires[0] + personas[1]: the submitter is the identity whose create fires the flow, so it needs create on the trigger object. On stock `showcase_field_zoo` that is the admin or a `showcase_contributor`-bound member. Mei and Ada answer 403 | #27409 | | approvals.request-page-reply-and-reader-visibility | 53 | acceptance[2] + verify + steps[2] + the request-read-gate pin claim: the 404/total leg binds a caller the app grants read on `sys_approval_request` (ADR-0103 engine-owned, no stock grant). A caller without the grant gets 403 with no row, and that is its own leg | #27409 | | approvals.record-page-decisions | 104, 105 | steps[9] / acceptance[4]: the override-only admin's levers fold into the `Admin override` menu (`declared-actions-override-menu`). steps[3], steps[7] / acceptance[3]: under a locking approval Edit is rendered **disabled**, pencils are hidden, and the band reads `Locked for approval` (vs `In approval · editable`). A hidden Edit now fails the clause | #27441 | | approvals.inbox-metadata-actions | 106 | fixtures.requires[2]: EXP-2001 also routes Ada Auditor (finance group, plain member, `can_override` false). The reassign detour stays only for EXP-DEMO | #27441 | | approvals.quorum-m-of-n | 107 | acceptance[4]: the tally matches at each **pending** stage. After the decision neither the page nor the read carries `decision_progress` | #27441 | | approvals.per-group-signoff | 108 | acceptance[2]: the same, for the chips (`pending_approver_groups`) | #27441 | | dashboards.chart-type-matrix | 111 | acceptance[4].verify: the metric tile's chrome differs from the synonyms' even on one dashboard, so only values are compared. steps[4]: the stock seed gives 2 month buckets (3 across two month boundaries) | #27441 | | dashboards.analytics-exposure-gate-and-measure-aggregate | 233 | fixtures.requires[2]: the stock specimens are `sys_jwks` (404 OBJECT_API_DISABLED) and `sys_session.token` / `sys_api_key.key` (400 INVALID_FIELD). Only the configured-cube legs need the scratch app, and its objects need the namespace prefix | #27438 | | i18n.strict-translation-key-rejection | 113 | steps[0]: the stale `name`-describe warning is dropped, since the describe now says snake_case. New variant: locale shape (`Chinese`, `zh_CN`), refused 422 at `locale`. acceptance[0].verify says how that variant's refusal reads | #27441 | | i18n.notification-localized-and-clears | 114 | steps[4], steps[5]: name the bell's inbox popover. The Home card is a second door that marks the row read first | #27441 | | i18n.surface-matrix | 115, 232 | steps[7]: the contact sections are read on the default form (Edit). The New dialog shows the create form's `who_is_this` (这是谁). automated.ref: the ui pins run from `ui/`. acceptance[7]: 16 per-app / 17 platform / 16 served groups, listed by name | #27441 (+#27438) | | search.console-global-search | 123 | steps[4] / acceptance[3]: the app segment of the record route is the package id (ADR-0048; `canonicalAppPathname` rewrites a name alias) | #27441 | | search.field-scoped-narrowing | 162, 170 | new step + acceptance[4]: an override naming no field is refused (GET blank/comma → 400 VALIDATION_ERROR; POST `[]` → 400 VALIDATION_FAILED at the body schema; `['']` → 400 INVALID_FIELD). Only an absent override scans the default set. automated.ref names `data-empty-search-fields-door.test.ts` as corroboration | #27438 | | search.global-search-skips-unreadable | 169 | fixtures.requires[2]: `last_login_ip` is stamped with the peer address on a direct sign-in (`127.0.0.1` locally), not null. `role = user` stays the V | #27438 | | ux-conventions.write-feedback | 126, 127, 128 | variants[4], [5], knownGaps[0]: the successMessages are spelled as declared today (the `{{ }}` templates). variants[1] names the task Grid view, the only list with `inlineEdit: true`. steps[4](a): Backlog → Done is driven through the API (400 invalid_transition), because the UI pickers offer only legal moves | #27441 | **One bounded in-place fix beyond the census rows** (same item, same defect class, mechanical): `ux-conventions.write-feedback` knownGaps[1] quoted the approve successMessage in the retired `${…}` spelling. It now reads `` `Approved "{{ result.request.record_title }}"` `` as declared in `sys-approval-request.object.ts`. Rows held on #28345 are untouched, including `write-feedback` acceptance[1].verify. ## Tests Docs-only ledger change. No package code is touched, so there are no package tests to run. ## Acceptance notes - `search.field-scoped-narrowing`: the spec helper `resolveSearchFields` still reads an empty list as no narrowing, for in-process callers that pass no ingress. The clause says so and scores the Data API doors, which refuse the empty override first. - `approvals.request-page-reply-and-reader-visibility` acceptance[2]: on stock, the 404 leg is blocked(fixture) unless a scratch grant gives a persona read on `sys_approval_request`. This is now explicit in the verify. - Observation, not filed: `sys_user.last_login_ip`'s field description still says the value comes "from the trusted proxy forwarded header". Without `server.trustProxy` it is the peer address (`plugin-auth` `client-address.ts`). ## Verification <details><summary>Local gates at head 7397bb1 (dispatch-gates --commands, 16 commands)</summary> ```text head 7397bb1 · dispatch-gates --commands --repo objectstack-ai/objectstack: EXIT=0, 16 commands exit 0 node scripts/check-adr-links.mjs exit 0 node scripts/check-adr-links.mjs --self-test exit 0 node scripts/check-closing-keyword-parity.mjs exit 0 node scripts/check-closing-keyword-parity.mjs --self-test exit 0 node scripts/check-comment-mask-corpus.mjs exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions exit 0 pnpm check:ci-filter-parity exit 0 pnpm check:cross-package-test-inputs exit 0 pnpm check:doc-authoring exit 0 pnpm check:driver-memory-census exit 0 pnpm check:gitlink-declared exit 0 pnpm check:nul-bytes exit 0 pnpm check:platform-checklist exit 0 pnpm check:refd-timer-probe exit 0 pnpm check:watch-hint-literal check:doc-formula-expressions first exited 3 (PREREQUISITE NOT MET: formula/lint dist absent); after turbo build of @objectstack/formula + @objectstack/lint under os-verify-lock (VERDICT command-exit 0) it exits 0 dispatch-gates --ran: 16 derived, 16 run, 0 NOT-MEASURED (a DERIVED zero — all 16 recorded an exit code) check-platform-checklist: OK — 16 areas, 352 items; symbol anchors 1073/1083 resolved, 10 on the named residual ``` </details> Re-check that the history is a prefix, done by script against `22ce3d13`: all 16 changed items read `revision` +1, `history.length` +1, the old history is a byte-equal prefix, and the last row's revision equals the item's. No area-level block changed, and the item count and order are unchanged. --- _Generated by [Claude Code](https://claude.ai/code/session_01QUs3BrWDv9TR18p6R88RwQ)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent ef0f6a1 commit edf9714

5 files changed

Lines changed: 141 additions & 62 deletions

File tree

0 commit comments

Comments
 (0)