Repository navigation
Description
Filed by the services-lane PM (session_01USNUyHEr7uaU6MoEWXitei) out of #5199, whose two successive scopes were both disproven by measurement. This card carries the one gap that survived both, with fresh evidence. #5199 is closed as falsified; see its closing comment for the chain.
2026-08-08 裁决落地(维护者批复 B):本单收窄为 services 半边——queryDataset 两处富化 + dataset-compiler.ts:374/406 改调共享解析器,并盖掉 d.name 替换。契约半边(共享 I18nLabel → string 解析器)见 #6765,归 spec 座位。裁决全文见下方评论。
Blocked-by: #6765
已测量的事实(全部实测,非推断)
在 origin/main (a5d2573) 的专用 worktree 上直接跑查询,两个策略各测:
dataset 声明的 label |
实测下发的 fields[] |
|---|---|
label: 'Owner'(字符串) |
[{name:'owner',type:'string',label:'Owner'}, …] —— 两个策略一致 |
label: { en:'Owner', 'zh-CN':'负责人' }(内联 map) |
[{name:'owner',type:'string'}, …] —— label 整个不见了,两个策略一致 |
| 不声明 | 同上,无 label |
成因:AnalyticsService.queryDataset 的两处富化(measures ~:1095-1130、dimensions ~:1161-1177)以 typeof … === 'string' 为门槛,非字符串一律跳过;再往上一层 dataset-compiler.ts:374 / :406 的 label: typeof d.label === 'string' ? d.label : d.name 把它替换成机器名,于是 /analytics/meta 还会把 owner 当作展示标题发布出去。
消费端早已就位:三处渲染器都经 buildDatasetFieldHelpers 把 fields[].label 排在第一顺位 —— objectui plugin-dashboard/src/DatasetWidget.tsx:563、app-shell/.../previews/DatasetPreview.tsx:128、plugin-report/src/DatasetReportRenderer.tsx:440/703/845。
内联 map 不是假想形状:I18nLabelSchema 自 #5728 起授权它,平台自己的三个已发布页面就有 31 处在用。中文部署照着写,拿到的是没有标签。
线型约束(实施时的硬边界)
线上仍是解析后的字符串,这是被两端声明钉死的:packages/spec/src/contracts/analytics-service.ts:40 写 fields[].label?: string,objectui 的 DatasetResultField.label?: string 与之匹配,且 headerLabel 把它当纯字符串 fallback 喂给 fieldLabel(...) —— 该路径上没有 pickLocalized,原样塞一个 map 会渲染成 [object Object]。
dataset-compiler 的 d.name 替换,否则 /analytics/meta 继续拿机器名当展示标题。
if (f.label == null) / if (f.label != null) continue 守卫,所以任何在上游写入占位值的做法都会永久顶掉文档提供的真标签 —— 这正是 #5199 route A 被判定为「有害而非冗余」的原因。
决策记录(已裁,存档)
修法必须回答「谁来解析、解析器住在哪」,而这个仓今天后端零个内联 map 解析器(已全仓 grep 核实:除 packages/spec 文档与 objectui 外无命中)。三个选项 A(服务内私有孪生)/ B(spec 共享解析器)/ C(拓宽线型交客户端)的完整权衡见下方裁决评论;维护者裁 B。
相邻卡(不同机制,非重复)
- Report charts render the raw dataset measure
nameas the axis title, ignoring the measure'slabel— the same measure on a dashboard chart resolves correctly #5141 —— report 渲染器拿到 measure label 却不用(渲染端;其前提预设了下发,与本次测量一致); - Analytics surfaces resolve select-option labels but never apply the i18n bundle — reports and dashboards show English while lists and forms show the translation #5076 —— analytics 面上 i18n bundle 未被应用(select 选项的行值,另一机制);
- i18n:
report与dataset完全没有 translator —— dataset 看板的表头/度量标签永远是英文(#4854 的范围外发现,拆单跟踪) #5199 —— 本卡出处,已关闭(两次范围均被证伪)。
来源:#5199 的 dev 实测回报(会话 session_01USNUyHEr7uaU6MoEWXitei 派发)。测量探针留在 worktree /home/user/objectstack-issue-5199/packages/services/service-analytics/src/__tests__/zz-probe-5199.test.ts(未跟踪),可复跑。
Activity
os-project-manager commentedon Aug 8, 2026
Maintainer ruling — 2026-08-08: option B. The maintainer reviewed the PM's three-axis analysis in session and accepted the recommendation (「同意 B」). Recorded by the services-lane PM;
needs-user-decisioncomes off with this comment.Decision: the inline locale map is resolved server-side, to a string, by one shared resolver living in
packages/spec— not by a private twin insideservice-analytics(option A), and not by widening the wire toI18nLabelfor the client to resolve (option C).Rationale on the record: the backend has zero inline-map resolvers today, so whichever option ships plants the first one; planting it inside a single service guarantees the next producer copies the copy (PD#12). Option C is defensible and arguably more consistent with how the platform treats maps elsewhere, but it buys that consistency with a wire-shape change across two repos plus every existing
label?: stringconsumer, and server-side resolution reaches the same user-visible result without it.Execution — contract-first split (shared-contract rule:
packages/specalways belongs to the spec seat, same path as #5712→#5933 and #5548→#6617):I18nLabel → string共享解析器 —— #6761 裁决 B 的契约半边(须与 objectuipickLocalized同规则,否则同一个 map 两端渲染不同) #6765: the sharedI18nLabel → stringresolver. Its acceptance hinge is not the API shape but rule parity with objectui'spickLocalized— fallback chain, region-variant handling (zh-CNvszh), and the miss case must agree, pinned by a test rather than asserted in a comment. Two resolvers that disagree would render the same metadata differently on the two ends with neither side erroring: exactly the fork this ruling exists to prevent.queryDataset's two enrichment sites plusdataset-compiler.ts:374/406— the latter'stypeof d.label === 'string' ? d.label : d.namesubstitution must be covered too, or/analytics/metakeeps publishing a machine name as a display title. Labels move topm:blocked; theBlocked-by:line is appended to the body so the unlock sweep returns this card automatically when spec: 后端第一个I18nLabel → string共享解析器 —— #6761 裁决 B 的契约半边(须与 objectuipickLocalized同规则,否则同一个 map 两端渲染不同) #6765 closes.Generated by Claude Code
os-project-manager commentedon Aug 9, 2026
UNBLOCK + CLAIM — services-lane PM session
session_01USNUyHEr7uaU6MoEWXitei, branchclaude/issue-6761-dataset-label-resolve.Unblock verified, not assumed: the body's
Blocked-by: #6765is satisfied — #6765 closedcompletedat 2026-08-09 04:33Z, so the sharedI18nLabel → stringresolver the ruling requires now exists inpackages/spec.pm:blocked→pm:queueswapped with this comment; dispatched the same patrol.Per #6465, the dispatch pins a re-verify at the merged ref as the dev's first action: read what #6765 actually shipped (exported name, signature, locale-fallback semantics, and whether the parity-with-
pickLocalizedtest landed) rather than assuming the candidate signature this card's sibling sketched — and if the delivered resolver cannot express what the two enrichment sites need, STOP and report rather than writing a second resolver, which is the entire point of ruling B.Generated by Claude Code
os-project-manager commentedon Aug 9, 2026
ACCEPT — PR #6951, reviewed by the
domain:servicesseat (#6021). Marking ready and queueing.The dev agent's JSON report never arrived (the subagent is gone from the host's task list, so there is nothing left to probe or revive). Accepted via the direct-acceptance path instead: draft PR present, all 25 check runs completed with conclusion
success— including both gate-carrying jobs by conclusion, ESLint (06:18:11Z) and TypeScript Type Check (06:25:35Z) — and the diff verified file-by-file against the claim's declared surface rather than against the PR body's own claims.What was verified against the diff
analytics-service.ts,dataset-compiler.ts.packages/specuntouched, objectui untouched — this PR consumes the contract half (spec: 后端第一个I18nLabel → string共享解析器 —— #6761 裁决 B 的契约半边(须与 objectuipickLocalized同规则,否则同一个 map 两端渲染不同) #6765 / PR feat(spec):resolveI18nLabel— the sharedI18nLabeltostringresolver, pinned to objectui'spickLocalized#6901) and adds nothing to it.resolveI18nLabelfrom@objectstack/spec/ui, with the reason written at the import site: a private twin here is the exact fork the ruling exists to prevent.I18nLabel → string共享解析器 —— #6761 裁决 B 的契约半边(须与 objectuipickLocalized同规则,否则同一个 map 两端渲染不同) #6765's delivered signature has nofallbackparameter and answersundefinedrather than''on a miss; the PR is written against that and says so, instead of against the non-binding signature in the card body.if (f.label == null)trap is avoided, and proven rather than asserted. On the wire a miss writes no key at all. In the compiler the machine-name fallback is unchanged and cannot pre-empt anything downstream, because a cube label never reachesAnalyticsResult.fields[]— established by reading all four producers of that shape (both strategies'buildFieldMeta, the preview evaluator, andDatasetExecutor's [17.0-rc2验收] analytics: 带 measure-scoped filter / derived 度量的 dataset 查询,响应 fields 丢失维度描述符 → 表头回退成原始维度名(如 "owner" 而非 "Owner") #5537 descriptor adoption), not by assertion.REGISTRY_LOCALEis a named constant carrying the argument that a compiled Cube is a registry artifact andgetMeta()takes no execution context — so baking a request locale there would let onezh-CNquery make/analytics/metaanswer whoever queried last. A test pins that it does not./analytics/meta's lying title is fixed too — the machine name published as a display title was the worse half of the defect and it did not get left behind.f.label == nullguard; the dimension site keeps its overwrite semantics. The dev extended both to maps without "tidying" either into a behaviour change.Scope is clean: no
content/docs/releases/edits, changeset present and correctlypatch(user-visible, no interface added or removed).Generated by Claude Code
test(spec): the third ui/ file group's test titles state each cited d…