Skip to content

plugin-approvals: the admin_rescue system comment is written into the approval timeline as English prose, so a zh-CN viewer reads it untranslated #25576

Closed

Description

@objectstack-fleet

What happens

On a record's 审批 / Approvals tab, every timeline entry names the step by a title-cased version of the node id, not by the node's configured label:

提交 · Dev Admin · Review Reviewer
通过 · Dev Admin · Review Reviewer
提交 · Dev Admin · Review Director

The flow declares Chinese labels for those nodes, and the server already stores them. From sys_approval_action for the same request:

{ "step_name": "review_reviewer", "step_label": "初审人审批", "action": "approve", ... }

sys_approval_request.current_step_label is also 初审人审批. So the data is right; the console timeline renders step_name (humanized) rather than step_label.

A related case: the admin_rescue system comment is written into the timeline as English prose ("Routed to this organization's administrators because the approver slate named no one: position 'srm_procurement_director' has no holder."). It shows up untranslated on a zh-CN console.

Repro

  • @objectstack/* 18.0.0, objectstack dev, locale zh-CN.
  • App: objectstack-ai/hotsrm main, flow srm_admission_approval. Its nodes are review_reviewer (label 初审人审批) and review_director (label 采购总监审批).
  • Create an application, set its status to admission_review, and approve both steps.
  • Open the record and go to the 审批 tab.

Expected

The timeline shows 初审人审批 / 采购总监审批 (step_label, or the translated label once #24863's contract applies). System comments use the viewer's locale.

Related


Re-filed as objectstack-fleet[bot] from #25471, same body and labels. #25471 was written off the fleet relay, under the maintainer's personal account, by a project-thread session through GitHub MCP; pm-dispatch allows content writes only through the relay. #25471's thread (triage, ruling, comments) stays there for reference; this card replaces it in the queue.
Session: session_018fnREjMeK9XQeHDwkJXKMW

Activity

  1. added
    bugSomething isn't working
    area:i18nThe customer's own language, across UI, metadata and notifications
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed on Oct 24, 2026
  2. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01JVTd3PprStGa26aez5omLB
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-25576-admin-rescue-comment-i18n
    Worktree: objectstack-issue-25576
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/plugins/plugin-approvals/src/** (the admin_rescue system comment near approval-service.ts:3905 on e649c2d91 and its read path; ⛔ not the SLA-reassign region PR #25600 edits), the plugin's translation bundles if it has them, tests, one changeset (stop on breach; explain in the report)
    Container & model: S, not mechanical (the stored shape is a design call), so M treatment; mode:cloud (maintainer: 「并发加到5,可以用云卡片」), model: TIER_DEFAULT (dispatch-gates --tier: "no path-derived mandate … the tier stays the PM's per-card judgment call"), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: plugin-approvals writes the sentence as stored English prose when the admin-rescue route fires | the platform's localized-text path for server-written system text; the dev names the precedent it reuses | a zh-CN (or any non-English) viewer of an approval timeline, whenever the admin-rescue route fires; measured in the hotsrm evaluation on 18.0.0
    Thread-read: none
    Serial constraints cleared: PR #25600 (seat 1, #25598) edits approval-service.ts in the SLA-reassign bodies (hunks near :5604 and :7040–:7131), away from :3905; whichever lands second merges main. #25573's runtime half is pm:blocked on #25609, not in flight.
    Premise: triage's audit is 6371325002 on #25471 (the card this one re-files). Re-read on origin/main e649c2d91: approval-service.ts:3905 still composes `Routed to this organization's administrators because the approver slate named no one: ${routedToAdministrators}.` as the stored comment.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25576,
    "status": "done",
    "branch": "claude/issue-25576-admin-rescue-comment-i18n",
    "pr": "#25627",
    "session": "session_01QeMwrPsf2zjo2pQLBzdWFV — this run's harness-stamped id (dispatched by domain:server seat 2, session_01JVTd3PprStGa26aez5omLB)",
    "premise_still_valid": true,
    "summary": "Premise held on origin/main e649c2d: openNodeRequest composed the admin_rescue note as English prose stored in sys_approval_action.comment, and listActions served it as stored. The note is now stored as a code plus parameters on the request's node-config snapshot (__adminRescueNote: {action_id, abandoned, fallback?}; no column, object schema or spec contract moves). Its sentence and clauses are new entries in the plugin's existing messages catalog (en/zh-CN/ja-JP/es-ES). listActions overlays the named row's comment, in place, at the reader's ExecutionContext.locale through the existing reader-locale-labels.ts path (#24863), extended by one member ReaderLabels.sentence. The stored comment stays the byte-identical English (one source: the en catalog entries). Rows stored before this change carry no snapshot entry and are served as stored; no migration. #25600 landed mid-flight and main was merged cleanly at b9c264f.",
    "tests": "Head b9c264f, through os-verify-lock: pnpm --filter @objectstack/plugin-approvals typecheck exit 0; pnpm --filter @objectstack/plugin-approvals test -> Test Files 96 passed (96), Tests 1259 passed (1259), VERDICT command-exit 0. New: admin-rescue-note.test.ts (4 cases) and 5 cases in admin-rescue-named-slate.integration.test.ts on real ObjectQL+SqlDriver+createMemoryI18n loaded with ApprovalsTranslations: zh-CN listActions reads '审批人名单未指向任何人,因此已转交本组织管理员处理:岗位「sales_manager」无人任职。'; the stored row keeps the English; en/no-locale readers get the English; CONTROL: a pre-change request and a neighbouring system comment stay as stored. Ablation, via scripts/ablation-replace.mjs: deleting the localizeAdminRescueNote call (anchor 1->0, blob 3527886551f8->0c2167e97d4a) gave 3 failed | 13 passed (zh-CN, fallback, CONTROL red). Restore: blob 3527886551f8 = HEAD, git diff HEAD empty. Source imports resolve to src (relative import), so no dist build was needed for the ablation leg.",
    "gates": "Local, head b9c264f: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 72 commands (same list pre- and post-merge); all 72 run, every exit 0, codes captured per command before any pipe. --ran reconciliation: '72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN' exit 0. Pre-merge run on f40e836 had check-plugin-teardown-shape --self-test exit 3 (shallow clone: pinned fixture commit 621a487 unreachable; fixed by fetching that one commit) and check:dts-closure exit 1 (plugin-approvals dist cleaned mid-turbo-rebuild running concurrently; 0 on re-run). check:i18n: 'check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)'. eslint --no-inline-config on the 8 touched .ts files: exit 0. CI check-runs on #25627: not yet concluded at report time (in_progress is the honest value; the seat reads them).",
    "line_budget": "S card; 9 files, +426 / -45 = 471 changed lines vs origin/main (dispatch-gates: under the 3000 human-merge threshold)",
    "files_changed": [
    ".changeset/25576-admin-rescue-comment-i18n.md",
    "packages/plugins/plugin-approvals/src/admin-rescue-named-slate.integration.test.ts",
    "packages/plugins/plugin-approvals/src/admin-rescue-note.test.ts",
    "packages/plugins/plugin-approvals/src/admin-rescue-note.ts",
    "packages/plugins/plugin-approvals/src/approval-notification-text.ts",
    "packages/plugins/plugin-approvals/src/approval-service.ts",
    "packages/plugins/plugin-approvals/src/approver-address-readers.test.ts",
    "packages/plugins/plugin-approvals/src/reader-locale-labels.ts",
    "packages/plugins/plugin-approvals/src/translations/messages.ts"
    ],
    "deviations": [
    "Worktree: the harness had already created the outcome branch on the primary checkout (at e649c2d), so worktree add -b was refused. I detached the primary checkout and added ../objectstack-issue-25576 on the existing branch. The branch is platform-specified, so no empty probe push.",
    "File surface: approver-address-readers.test.ts (same package, outside src/** only in being a test ledger) gained one PERSON_SITES classification for the new actor_id presence read. That pin requires every read of the column to be classified; no gate was weakened.",
    "Commit trailers are the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude); the harness reminder's model-named trailer was not used.",
    "Closure build #1 was pnpm --filter '@objectstack/plugin-approvals^...' build --concurrency=2; that flag after the filter is forwarded to the scripts (a toolchain trap), harmless here."
    ],
    "fixed_overhead": "pnpm install --frozen-lockfile: 40 s (Done in 36.6s). Build closure via the os-dev recipe (pnpm --filter plugin-approvals^... build, 34 projects, no turbo): 446 s. Turbo remote cache: NOT AVAILABLE in this container. The i18n gate's prescribed turbo closure printed 'Remote caching disabled, using shared worktree cache' with 0 cached / 61 tasks, 703 s, so the remote-cache pull is 0 s by absence, not a measured pull. Post-merge turbo rebuild: 17/33 cached (local), inside a 384 s locked run that also did typecheck + test.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "3 — mcp__github__issue_read x3 (get #25576, get_comments #25576, get_comments #25471); no write tool",
    "api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft #25627) + POST /repos//issues/25627/assignees; (2) this report -> POST /repos//issues/25576/comments. git push is not counted.",
    "open_questions": [
    {
    "question": "Stored shape (implementation trade-off made in-card; recorded, not blocking): where do the code and parameters live?",
    "options": [
    "A — request node-config snapshot __adminRescueNote naming the action row (chosen): no column, no object-schema/bundle churn, no API shape change; precedent __flowLabel/__round/__resolvedFrom ('Stamped on the snapshot ... so no schema migration').",
    "B — new columns on sys_approval_action (comment_key/comment_params): the code sits on the row itself, but it grows a platform object served by the generic data door, plus 4-locale field labels, for one system sentence."
    ],
    "recommendation": "A, because it reuses the existing open-time snapshot and reader-locale overlay with no contract surface added (start-up axis: no new declaration surface). A second system note needing a code would be the pull for B."
    },
    {
    "question": "objectui half (not measured: objectui not attached): does the console's record Approvals tab read the timeline through GET /api/v1/approvals/requests/:id/actions (localized now) or through the generic data door on sys_approval_action (still the stored English)?",
    "options": [
    "A — it uses the approvals actions route: nothing more to do",
    "B — it reads sys_approval_action directly: objectui switches the timeline to the approvals door"
    ],
    "recommendation": "Seat to check RecordApprovalsPanel's read in objectui and file the objectui half only if B."
    }
    ],
    "out_of_scope_findings": [
    "carrier: domain:server seat 2 (sunhaolin) — the objectui read-path question above; noted, not filed (unmeasured from this container)"
    ]
    }

  4. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25627 comment 6383830466 (head b9c264fd8f; landing in the same act) · domain:server seat 2 · session_01JVTd3PprStGa26aez5omLB


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #25627 → ca80f05ec · domain:server seat 2 · sunhaolin · session_01JVTd3PprStGa26aez5omLB · 2026-10-24T12:11Z

    reading result
    PR #25627 object merged: true, merge commit ca80f05ec5c6aab22f31553cb5f8737a17450563, merged 2026-10-24T12:11:03Z through the merge queue
    origin/main ca80f05ec is an ancestor of origin/main, single parent (squash)
    queue branch no gh-readonly-queue/main/* head for PR 25627 remains
    • State: closed completed by Fixes #25576 on merge. pm:dispatched stripped in this act.
    • Delivered: the admin_rescue timeline note is stored as a code and parameters, and GET /api/v1/approvals/requests/:id/actions renders it in the reader's language (en, zh-CN, ja-JP, es-ES). The console's record Approvals tab reads that door (objectui useRecordApprovals.ts), so nothing is owed in objectui. Older requests keep their stored English.
    • Cloud session session_01QeMwrPsf2zjo2pQLBzdWFV archived in this act.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions