Repository navigation
Commit edf9714
<!-- 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
- docs/qa/platform-checklist/areas
0 commit comments