You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
server-authored UI text (family card): the approvals drawer's Approve says only "Approved.", and Explain access's Principal resolution prose names the user by raw id #23157
Filing-gate category ① (product defects, named landing points) and ③ — the maintainer's order in the dispatching session, verbatim: 「测试遇到的问题,可以提交issue」.
Reader who acts: the seat(s) owning plugin-approvals and plugin-security (triage routes; both rows are server-authored strings the console renders verbatim). A family card: one row per string. Dedupe: semantic issue search on this repo, closed included, "approval approve successMessage Approved names no request explain access principal raw user id holds positions" — 18 hits, none covers either string.
Environment
objectstack 36b2889c with the console built from objectui 1fb1ced36243 (the pin hop of #23104, since landed as f451a5964), stock examples/app-showcase, admin persona (and a member where noted). Every row reproduced at least twice on fresh loads by the runner; none is a regression of the pin hop (no hop commit touches the files named).
Rows
#
Location (objectstack main)
Reproduction
Expected
1
packages/plugins/plugin-approvals/src/sys-approval-request.object.ts:474 — the approve action's declared successMessage: 'Approved.'
/_console/apps/setup/component/approvals/inbox → open a pending request (e.g. "Invoice Dual Sign-off (parallel approval)") → drawer → Approve → fill Comment → Confirm. POST /api/v1/approvals/requests/ID/approve 200 and the toast is the bare verb "Approved." (reproduced on INV-1001 and EXP-2001 requests). The inline row Approve on the same page already says Approved "EXP-DEMO".
Success text naming what was approved, as the inline path does.
2
packages/plugins/plugin-security/src/explain-engine.ts:2061 — Principal ${context?.userId …} holds position(s) …
Studio → a writable package → More → Access → Explain access → User "Me (calling principal)", any object, Read → Explain → Evaluation pipeline › Principal resolution reads "Principal eycWvq… holds position(s) [platform_admin, …]" (2 fresh runs). The panel's own Principal line above already renders "Dev Admin admin@objectos.ai" with the id as a secondary line.
The person (name / email), the id at most secondary. The console half of this family is objectstack-ai/objectui#11862.
Routed on the maintainer's direct order · PM seat domain:devx#2, session_016JB93R2KcQP4GgKaUnseVF · 2026-10-13T05:31Z
The maintainer, verbatim, in that session: 「按这 5 类开父单,派给 objectui 车道,以及:820px 宽度下面包屑重叠。* 每个新库首屏都弹时区设置。* Home 页的应用卡片不是链接,没法在新标签页打开。」
This card is a member of the parent objectstack-ai/objectui#12308, classes 2 and 5 (feedback that names nothing; labels over raw ids). Its fix lands here, in packages/plugins/ (domain table: domain:services), so it goes to that lane's queue rather than objectui's: domain:services · pm:queue · priority:p3 · area:workflow, written with this comment. Triage may re-grade; the routing is the maintainer's.
Claim: PM loop round 8 · 2026-10-13T07:01Z
Session: session_01FzQy26KznSa5j2gnf3AzsE
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-23157-server-authored-ui-text
Worktree: objectstack-issue-23157
Domain: domain:services
Seat: domain:services#2 (seat post #21118)
File surface: row 1 packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (the approve action's successMessage, plus its translation bundle values if they carry it); row 2 packages/plugins/plugin-security/src/explain-engine.ts (the principal layer's detail sentence only); their tests; one .changeset/23157-*.md (stop on breach; explain in the report)
Container & model: M (two strings, each fed by data the measurement must locate), mode:subagent, model: default — dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate … The tier stays the PM's per-card judgment call"
Clause-②: no — two server-authored strings name the record and the person; no published schema, translation key or export moves.
Responsibility: plugin-approvals' approve action and plugin-security's explain engine (the platform's own code) author the two strings | none: the console renders both verbatim, and its own inline path and Principal line already name the record and the person | every approver using the drawer's Approve, and every Explain-access user; measured on the QA run (#23104)
Thread-read: 6138965896
Serial constraints cleared: of the 13 open PRs, only PR #23179 (#23100, domain:spec, draft) touches explain-engine.ts, at +2/-1 lines away from the principal layer. That is ordinary concurrency, and the later lander merges main. explain-engine.ts is in the C2/C3 epic's declared territory (#6021 §3), so it is declared to #15194 first. No open PR touches sys-approval-request.object.ts. #22738 (domain:engine, approval-service.ts's enrichRows) and #22725 (domain:spec, the listRequests queue arm) are in other files.
os-dev-report
{
"issue": 23157,
"status": "done",
"branch": "claude/issue-23157-server-authored-ui-text",
"pr": "#23202",
"session": "session_01FzQy26KznSa5j2gnf3AzsE",
"premise_still_valid": true,
"summary": "Row 1: the approval_approve success copy is now Approved "${result.request.record_title}", in the inbox's inline Approve wording. The zh-CN, ja-JP and es-ES values carry the same token, and no key was added. H1 held: the approve route answers decide() unwrapped, and request.record_title is on it. Measured live on the showcase at 09218dd, a finalizing approve toasts Approved "INV-1006" and a still-pending approve toasts Approved "EXP-2001". Row 2: the explain engine's principal sentence, including the on-behalf-of delegator clause, now names the person first, e.g. 'Principal Dev Admin (admin@objectos.ai; id 1cnVm0j9...) holds position(s) ...' (live). The user row is read through the asker's own door (ql.findOne under the asker context, middleware included), so an asker whose read returns no row gets the bare id and a masked field stays out. The consequence is that the principal name can now differ by asker, so the 9 ruling-A whole-decision comparisons in explain-asker-gate.test.ts set aside only that person segment.",
"tests": "At HEAD 09218dd. (1) Full suites: plugin-security vitest 194 files and 4008 passed (45 skipped); plugin-approvals vitest 74 files and 1042 passed. Both ran under os-verify-lock, each VERDICT command-exit 0. (2) Both packages' typecheck scripts exit 0; plugin-approvals check:test-typecheck OK, debt unchanged. (3) New pins: explain-principal-person.test.ts 12/12, a real SecurityPlugin+ObjectQL+better-sqlite3 rig in which each asker's sys_user read is armed first. Controls: a blind manage_users admin gets 'Principal usr_pp_ada holds' and the whole decision lacks the name and email; a field-level-security masked admin gets the name and never the email; the D10 intersected-read cells assert what the read returns. approval-service.test.ts adds 4 cells (copy filled from a real decide() answer, finalized, pending and snapshot cases); approve-success-copy-i18n.test.ts adds 3. (4) Negative-pin ablations, each committed first, run through scripts/ablation-replace.mjs with an EXIT/INT/TERM trap, and restored with blob==HEAD and an empty git diff HEAD. No dist leg was needed, because the subjects resolve via relative src imports. A1, old principal sentence: 5 failed, e.g. "expected 'Principal usr_pp_ada holds position(s...' to match /^Principal Ada Lovelace \(ada@example.../", and the 7 controls held. A2, old delegator clause: 2 failed. B1, source copy back to 'Approved.': 7 failed, e.g. "expected 'Approved.' to be 'Approved \"Acme Renewal\"'". B2, zh-CN back to bare: 1 failed, the tokens check. (5) Live showcase boot on my own random port, then torn down: POST /api/v1/approvals/requests/ID/approve keys are decision,finalized,request,resumed,runId with no data envelope; GET /api/v1/meta/object/sys_approval_request serves the new copy in en and zh-CN; POST /api/v1/security/explain shows the self and other-user principal sentences named. (6) eslint --no-inline-config over the 10 changed TS files: 10 files, 0 errors, 0 warnings, counted by --format json. The population is eslint.config.mjs's own **/*.{ts,...} block. Invariance: the config enables no parserOptions.project (type-aware linting is off), so the diff cannot move a verdict on an untouched file.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, derived with no paths at 09218dd: 66 commands, all run, all exit 0 (including check:i18n, check:i18n-stale-fill, check:engine-double-contract, check:nul-bytes, check:issue-citations, check:doc-authoring, check:published-files, check:cross-package-test-inputs, check:test-source-alias, check-adr-0087-registration --base origin/main, check-empty-changeset --base origin/main). --ran reconciliation: '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero, all exit codes recorded). Left to CI: type-check lanes, Test Core, Build Core, dogfood.",
"line_budget": "11 files, +473/-12 (code: explain-engine.ts +45/-4, sys-approval-request.object.ts +7/-1, 4 bundle values +1/-1 each; tests +404/-3; changeset +13)",
"files_changed": [
".changeset/23157-server-authored-ui-text-names-what-and-who.md",
"packages/plugins/plugin-approvals/src/sys-approval-request.object.ts",
"packages/plugins/plugin-approvals/src/translations/en.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/zh-CN.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/ja-JP.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/es-ES.objects.generated.ts",
"packages/plugins/plugin-approvals/src/approval-service.test.ts",
"packages/plugins/plugin-approvals/src/translations/approve-success-copy-i18n.test.ts",
"packages/plugins/plugin-security/src/explain-engine.ts",
"packages/plugins/plugin-security/src/explain-principal-person.test.ts",
"packages/plugins/plugin-security/src/explain-asker-gate.test.ts"
],
"deviations": [
"explain-engine.ts: besides the principal detail sentence and its delegation clause, the change adds one module-private helper, personOf (placed just above explainOnce), and two awaited reads ahead of layers.push. It does not touch isRlsDenySentinel, its docblock, or the lines PR #23179 edits.",
"explain-asker-gate.test.ts (ruling-A pins) was edited: its 9 cross-asker whole-decision equalities went red because the principal is now named per asker. They now compare through apartFromPrincipalPerson, which blanks only the person segment of the principal sentence. This is within the claim's 'their tests' surface, but it is a pin edit the PM should see.",
"label-write: zero writes. The dispatch named no label, and skip-changeset does not apply because the PR publishes and carries a changeset.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude) rather than the harness reminder's model-named Co-Authored-By."
],
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "2 relay strokes, both as objectstack-fleet[bot] (git push x5 is not REST and goes direct): (1) POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create, which became POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/23202/assignees ['zhuangjianguo'], run 38367691682 success, read-back 9430/9430 bytes identical; (2) the os-dev-report comment on #23157 via scripts/pm/post-stamped.mjs",
"open_questions": [],
"out_of_scope_findings": [
"carrier: this family card's PM, to add as rows on #23157 / objectui#12308 class 2 · noted, not filed. Other success copies on sys_approval_request are still bare verbs: approval_reject 'Rejected.' (its inline path says Rejected "TITLE", and the reject route answers the same decide() result, so the same token applies), plus 'Reassigned.', 'Sent back for revision.', 'Information requested.', 'Reminder sent.', 'Recalled.', 'Resubmitted.' and 'Reply posted.'. Read from source only; the toasts were not reproduced in the UI. Dedupe words: approval reject successMessage Rejected bare verb, sys_approval_request successMessage names no request, approvals drawer toast bare verb",
"carrier: 承接者:无 · noted in Acceptance notes, not filed. The principal sentence's ADR-0091 D3 delegated-position clause ('approver from usr_X until ...') and its contributor via ('delegation from usr_X') still name a raw user id (objectui#12308 class 5). Not reproduced live, because the showcase seeds no delegated position. Dedupe words: explain principal delegated position from raw user id, ADR-0091 D3 delegation from id explain"
]
}
Claim amendment · domain:services seat 2 · session_01FzQy26KznSa5j2gnf3AzsE · 2026-10-13T08:17Z, under claim 6140175145. ⛔ Not a new claim. Same branch, same PR #23202; Clause-②: no is unchanged.
The seat's review of PR #23202 at 09218ddc1c against the dev's report sends one patch round. The rule: a published defect the round touches is fixed in the round. Same-family findings join this family card's rows, never a single-point card.
Principal layer, the sentence this PR edits: its ADR-0091 D3 delegated-position clause ("… from USER until …") and its contributor attribution ("delegation from USER") still print a raw user id. They are named through the same asker-door helper, personOf, in the same detail expression. The file surface stays the principal layer's sentence. The declaration to [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194 (6140183484) covers it, and the helper and its two reads are declared deviations.
New rows on this family card (class 2, "feedback that names nothing"): the other bare-verb successMessage copies on sys_approval_request. These are approval_reject "Rejected." and the reassign, send-back, request-info, remind, recall, resubmit and reply actions.
Each is fixed in this PR only where its route's answer is measured to carry request.record_title. The reject route answers the same decide() result as approve.
Each value is updated in step across the four bundles; ⛔ no new translation key.
A copy whose route answer does not carry the title stays as it is and is listed in the PR's Acceptance notes with that reading.
The file surface grows to those actions' successMessage values in sys-approval-request.object.ts and the four translation bundles.
Accepted as declared: the ruling-A pins in explain-asker-gate.test.ts now compare through apartFromPrincipalPerson. The record story ruling A governs is still compared in full. Only the principal's person label, read through each asker's own door, is set aside, consistent with ruling A's "by the read access of whoever ASKED".
os-dev-report
{
"issue": 23157,
"status": "done",
"branch": "claude/issue-23157-server-authored-ui-text",
"pr": "#23202",
"session": "session_01FzQy26KznSa5j2gnf3AzsE",
"premise_still_valid": true,
"summary": "Patch round (claim amendment 6141342138), at head dd9bb74. (1) The principal layer now names the user an ADR-0091 D3 delegated position came from, through personOf and the asker's door, both in the clause ('approver from Grace Hopper (grace@...; id usr_...) until ...') and in the position's contributor via ('delegation from ...'). Each distinct user is read once. A blind asker gets the bare id. (2) All 8 sibling request actions were measured live on the showcase, each at its route, and every answer carries request.record_title. The measured values: reject EXP-DEMO, reassign EXP-2001, revise and resubmit and recall 'Compliance Audit' (opened by raising a project budget so the revise edge exists), request-info, remind and comment INV-1012. So all 9 copies now name the request: Approve and Reject in the inline wording, the others as Reassigned "T", Sent "T" back for revision, Information requested on "T", Reminder sent for "T", Recalled "T", Resubmitted "T" and Reply posted on "T". Each is updated in step across the four bundles with no new key. The not-changed list is empty, and no route was widened. (3) The changeset is rewritten for the new rows. The new PR body is drafted at /tmp/claude-0/seat/issue-23157/pr-body-r2.md (11441 bytes, Fixes #23157 and the Clause-② line kept, ruling-A pin change stated as seat-accepted). It is NOT written to the PR; see deviations. (4) origin/main moved (to c6139f2) on no file this PR touches, and PR #23179 is still open, so there was no merge.",
"tests": "At HEAD dd9bb74, both full suites ran under os-verify-lock (slot issue-23157), VERDICT command-exit 0. plugin-security vitest: 194 files, 4012 passed, 45 skipped. plugin-approvals vitest: 74 files, 1051 passed. Both typecheck scripts exit 0, and test-typecheck held (plugin-approvals 324 errors / 27 signatures, unchanged). New and updated pins: explain-principal-person.test.ts now has 16 cells; the 4 new D3 delegated-position cells use a real sys_user_position row with delegated_from: an armed check, the directory admin named in clause and attribution, the masked admin with the name but no email, and the blind-admin control with the bare id in both and no name or email anywhere in the decision. approval-service.test.ts has 12 cells, each filling an action's copy from that action's real service answer (decide approve x3, decide reject, reassign, sendBack, requestInfo, remind, recall, resubmit, comment) plus a check that every declared copy has a token. success-copy-names-request-i18n.test.ts (renamed from approve-success-copy-i18n.test.ts) has 4 cells over all nine actions times four locales. New ablations, each committed first, run through scripts/ablation-replace.mjs with a trap, and restored with blob==HEAD and an empty git diff HEAD: C1, delegatorOf back to the raw id: 2 failed and the blind control held, "expected 'Principal Lin Delegate (lin@example.t...' to contain '1 position(s) held via delegation (AD...'". D1, reject back to 'Rejected.': 5 failed, including "expected 'Rejected.' to be 'Rejected \"Acme Renewal\"'". D2, ja-JP recall back to the bare verb: 1 failed, "ja-JP approval_recall.successMessage tokens: expected [] to deeply equal [ '${result.request.record_title}' ]". The round-1 legs A1, A2, B1 and B2 stand as reported. Live: a fresh showcase boot on my own random port, all 8 sibling routes answered unwrapped (keys recorded per route in the PR body draft), then torn down. eslint --no-inline-config over the 10 changed TS files: 10 files, 0 errors, 0 warnings, counted by --format json. The population is eslint.config.mjs's **/*.{ts,...} block, and the narrowing is invariant because no parserOptions.project (type-aware linting) is set.",
"gates": "Re-derived after the diff with dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) at dd9bb74: 66 commands, the same list as round 1. All 66 ran and all exited 0, including check:i18n ('all bundles in sync'), check:i18n-stale-fill, check:nul-bytes, check:engine-double-contract and check:issue-citations. dist was rebuilt for both packages first. --ran reconciliation: '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN', a derived zero with all exit codes recorded. Left to CI: type-check lanes, Test Core, Build Core, dogfood.",
"line_budget": "vs base d4aaeaf: 11 files, +660/-54. This round vs 09218dd: +275/-130. Code: explain-engine.ts +54/-6; sys-approval-request.object.ts +17/-9; 4 bundles +9/-9 each. Tests: +531/-3. Changeset: +22.",
"files_changed": [
".changeset/23157-server-authored-ui-text-names-what-and-who.md",
"packages/plugins/plugin-approvals/src/sys-approval-request.object.ts",
"packages/plugins/plugin-approvals/src/translations/en.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/zh-CN.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/ja-JP.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/es-ES.objects.generated.ts",
"packages/plugins/plugin-approvals/src/approval-service.test.ts",
"packages/plugins/plugin-approvals/src/translations/success-copy-names-request-i18n.test.ts",
"packages/plugins/plugin-security/src/explain-engine.ts",
"packages/plugins/plugin-security/src/explain-principal-person.test.ts",
"packages/plugins/plugin-security/src/explain-asker-gate.test.ts"
],
"deviations": [
"CONFLICT, role file over dispatch: the coordinator asked me to update the PR body. os-dev.md says the dev writes the PR body once, at pr_create, and never PATCHes it; later changes go to the seat with the edit named. I did not write the PR body. The full replacement body is at /tmp/claude-0/seat/issue-23157/pr-body-r2.md (11441 bytes, no less-than characters, no control bytes, only closing keyword 'Fixes #23157'). The seat writes it with an issue_patch body on #23202 and reads it back.",
"explain-engine.ts carries the declared helper personOf plus its reads: principal, delegator, and now one read per distinct delegated-position user, collected ahead of layers.push. Both the clause and the contributor via in the same expression changed. isRlsDenySentinel, its docblock and PR #23179's lines are untouched.",
"Test file renamed within this PR: approve-success-copy-i18n.test.ts became success-copy-names-request-i18n.test.ts, because it now pins all nine actions.",
"No merge of origin/main: it moved (to c6139f2, including a version-packages commit), but on no file this PR touches, and PR #23179 is still open.",
"label-write: zero writes, as in round 1 (no label named; the PR publishes, so skip-changeset does not apply).",
"Commit trailers use AGENTS.md's model-free pair rather than the harness reminder's model-named Co-Authored-By."
],
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "This round: 1 relay stroke, the os-dev-report comment on #23157 via scripts/pm/post-stamped.mjs (POST /repos/objectstack-ai/objectstack/dispatches, then POST /repos//issues/23157/comments as objectstack-fleet[bot]). git push x4 is not REST. No PR body write and no label write. Round 1's 2 strokes (pr_create plus assignees, and the first report comment) are unchanged.",
"open_questions": [],
"out_of_scope_findings": []
}
Seat ACCEPT: PR #23202 at dd9bb74c59 · round 1 plus one patch round
domain:services seat 2 · session_01FzQy26KznSa5j2gnf3AzsE · 2026-10-13T09:12Z, under claim 6140175145 and amendment 6141342138. Checked on GitHub and on origin/main, not from the reports.
Row 1, widened by the amendment to the family on sys_approval_request: all nine request actions' success copy now names the request through ${result.request.record_title}. Each route was measured live and answers with that title, and no route answer was widened. The four bundles were updated in step, with no new translation key; the bundles' generated header permits in-place edits.
Row 2: explain's principal sentence names the principal, the on-behalf-of delegator and each D3 delegated-position user as a person (name, then email, then id). Each is read through the ASKER's door (ql.findOne under the asker's context), so row-level and field-level security bound what the sentence carries. A blind asker gets the bare id.
The ruling-A pins:explain-asker-gate.test.ts now compares through apartFromPrincipalPerson. The record story ruling A governs is still compared whole. Only the person label, read through each asker's own door, is set aside. Accepted (amendment 6141342138).
pins: explain-principal-person 16 cells, including masked and blind controls; approval-service 12 cells filled from each action's real answer; the i18n tokens test covers 9 actions × 4 locales;
ablation legs A1, A2, B1, B2, C1, D1 and D2, each red only on its target, every restore proven by blob.
Changeset:plugin-approvals and plugin-securitypatch. The success copy and the explain prose change, and no accept set or published shape moves.
CI at dd9bb74c59 (latest run per name): 40 names, 34 success, 6 skipped, 0 red. The PR-body edit re-fires the PR-event checks, and the queue waits for them.
Writes:mcp_calls 0.
Out-of-scope findings: none left. The round-1 findings (the other bare-verb copies, and the delegated-position clause) were folded into this PR by the amendment.
Landing:pr_ready + automerge_enable through the relay. This seat follows it to MERGED.
Landed · PR #23202 merged through the merge queue at 2026-10-13T09:39Z as 920bc12a24, and that commit is on origin/main (read 2026-10-13T09:44Z). domain:services seat 2 · session_01FzQy26KznSa5j2gnf3AzsE, under claim 6140175145 and amendment 6141342138, ACCEPT 6142261622.
On main:
All nine request actions on sys_approval_request name the request in their success copy, through ${result.request.record_title}, in four locales with no new key.
Explain's principal sentence names the principal, the on-behalf-of delegator and each delegated-position user as a person, read through the asker's own door.
Queue path:ready_for_review, then auto_merge_enabled, then added_to_merge_queue at 2026-10-13T09:14Z. The PR's only closing keyword was Fixes #23157, and no other card closed in the window.
Board: this card closed completed by that keyword. This write clears pm:dispatched and the assignee; priority:p3, domain:services and area:workflow stay.
Publication:.changeset/23157-server-authored-ui-text-names-what-and-who.md (patch) ships at the next version release. A merge to main does not publish.
Activity
objectstack-fleet commentedon Oct 13, 2026
Routed on the maintainer's direct order · PM seat
domain:devx#2,session_016JB93R2KcQP4GgKaUnseVF· 2026-10-13T05:31ZThe maintainer, verbatim, in that session: 「按这 5 类开父单,派给 objectui 车道,以及:820px 宽度下面包屑重叠。* 每个新库首屏都弹时区设置。* Home 页的应用卡片不是链接,没法在新标签页打开。」
This card is a member of the parent objectstack-ai/objectui#12308, classes 2 and 5 (feedback that names nothing; labels over raw ids). Its fix lands here, in
packages/plugins/(domain table:domain:services), so it goes to that lane's queue rather than objectui's:domain:services·pm:queue·priority:p3·area:workflow, written with this comment. Triage may re-grade; the routing is the maintainer's.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 8 · 2026-10-13T07:01Z
Session:
session_01FzQy26KznSa5j2gnf3AzsEAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-23157-server-authored-ui-textWorktree:
objectstack-issue-23157Domain:
domain:servicesSeat:
domain:services#2(seat post #21118)File surface: row 1
packages/plugins/plugin-approvals/src/sys-approval-request.object.ts(the approve action'ssuccessMessage, plus its translation bundle values if they carry it); row 2packages/plugins/plugin-security/src/explain-engine.ts(the principal layer'sdetailsentence only); their tests; one.changeset/23157-*.md(stop on breach; explain in the report)Container & model:
M (two strings, each fed by data the measurement must locate),mode:subagent,model: default — dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate … The tier stays the PM's per-card judgment call"Clause-②: no — two server-authored strings name the record and the person; no published schema, translation key or export moves.
Responsibility:
plugin-approvals' approve action andplugin-security's explain engine (the platform's own code) author the two strings | none: the console renders both verbatim, and its own inline path and Principal line already name the record and the person | every approver using the drawer's Approve, and every Explain-access user; measured on the QA run (#23104)Thread-read: 6138965896
Serial constraints cleared: of the 13 open PRs, only PR #23179 (#23100,
domain:spec, draft) touchesexplain-engine.ts, at +2/-1 lines away from the principal layer. That is ordinary concurrency, and the later lander mergesmain.explain-engine.tsis in the C2/C3 epic's declared territory (#6021 §3), so it is declared to #15194 first. No open PR touchessys-approval-request.object.ts. #22738 (domain:engine,approval-service.ts'senrichRows) and #22725 (domain:spec, thelistRequestsqueue arm) are in other files.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23157,
"status": "done",
"branch": "claude/issue-23157-server-authored-ui-text",
"pr": "#23202",
"session": "session_01FzQy26KznSa5j2gnf3AzsE",
"premise_still_valid": true,
"summary": "Row 1: the approval_approve success copy is now Approved "${result.request.record_title}", in the inbox's inline Approve wording. The zh-CN, ja-JP and es-ES values carry the same token, and no key was added. H1 held: the approve route answers decide() unwrapped, and request.record_title is on it. Measured live on the showcase at 09218dd, a finalizing approve toasts Approved "INV-1006" and a still-pending approve toasts Approved "EXP-2001". Row 2: the explain engine's principal sentence, including the on-behalf-of delegator clause, now names the person first, e.g. 'Principal Dev Admin (admin@objectos.ai; id 1cnVm0j9...) holds position(s) ...' (live). The user row is read through the asker's own door (ql.findOne under the asker context, middleware included), so an asker whose read returns no row gets the bare id and a masked field stays out. The consequence is that the principal name can now differ by asker, so the 9 ruling-A whole-decision comparisons in explain-asker-gate.test.ts set aside only that person segment.",
"tests": "At HEAD 09218dd. (1) Full suites: plugin-security vitest 194 files and 4008 passed (45 skipped); plugin-approvals vitest 74 files and 1042 passed. Both ran under os-verify-lock, each VERDICT command-exit 0. (2) Both packages' typecheck scripts exit 0; plugin-approvals check:test-typecheck OK, debt unchanged. (3) New pins: explain-principal-person.test.ts 12/12, a real SecurityPlugin+ObjectQL+better-sqlite3 rig in which each asker's sys_user read is armed first. Controls: a blind manage_users admin gets 'Principal usr_pp_ada holds' and the whole decision lacks the name and email; a field-level-security masked admin gets the name and never the email; the D10 intersected-read cells assert what the read returns. approval-service.test.ts adds 4 cells (copy filled from a real decide() answer, finalized, pending and snapshot cases); approve-success-copy-i18n.test.ts adds 3. (4) Negative-pin ablations, each committed first, run through scripts/ablation-replace.mjs with an EXIT/INT/TERM trap, and restored with blob==HEAD and an empty git diff HEAD. No dist leg was needed, because the subjects resolve via relative src imports. A1, old principal sentence: 5 failed, e.g. "expected 'Principal usr_pp_ada holds position(s...' to match /^Principal Ada Lovelace \(ada@example.../", and the 7 controls held. A2, old delegator clause: 2 failed. B1, source copy back to 'Approved.': 7 failed, e.g. "expected 'Approved.' to be 'Approved \"Acme Renewal\"'". B2, zh-CN back to bare: 1 failed, the tokens check. (5) Live showcase boot on my own random port, then torn down: POST /api/v1/approvals/requests/ID/approve keys are decision,finalized,request,resumed,runId with no data envelope; GET /api/v1/meta/object/sys_approval_request serves the new copy in en and zh-CN; POST /api/v1/security/explain shows the self and other-user principal sentences named. (6) eslint --no-inline-config over the 10 changed TS files: 10 files, 0 errors, 0 warnings, counted by --format json. The population is eslint.config.mjs's own **/*.{ts,...} block. Invariance: the config enables no parserOptions.project (type-aware linting is off), so the diff cannot move a verdict on an untouched file.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, derived with no paths at 09218dd: 66 commands, all run, all exit 0 (including check:i18n, check:i18n-stale-fill, check:engine-double-contract, check:nul-bytes, check:issue-citations, check:doc-authoring, check:published-files, check:cross-package-test-inputs, check:test-source-alias, check-adr-0087-registration --base origin/main, check-empty-changeset --base origin/main). --ran reconciliation: '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero, all exit codes recorded). Left to CI: type-check lanes, Test Core, Build Core, dogfood.",
"line_budget": "11 files, +473/-12 (code: explain-engine.ts +45/-4, sys-approval-request.object.ts +7/-1, 4 bundle values +1/-1 each; tests +404/-3; changeset +13)",
"files_changed": [
".changeset/23157-server-authored-ui-text-names-what-and-who.md",
"packages/plugins/plugin-approvals/src/sys-approval-request.object.ts",
"packages/plugins/plugin-approvals/src/translations/en.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/zh-CN.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/ja-JP.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/es-ES.objects.generated.ts",
"packages/plugins/plugin-approvals/src/approval-service.test.ts",
"packages/plugins/plugin-approvals/src/translations/approve-success-copy-i18n.test.ts",
"packages/plugins/plugin-security/src/explain-engine.ts",
"packages/plugins/plugin-security/src/explain-principal-person.test.ts",
"packages/plugins/plugin-security/src/explain-asker-gate.test.ts"
],
"deviations": [
"explain-engine.ts: besides the principal detail sentence and its delegation clause, the change adds one module-private helper, personOf (placed just above explainOnce), and two awaited reads ahead of layers.push. It does not touch isRlsDenySentinel, its docblock, or the lines PR #23179 edits.",
"explain-asker-gate.test.ts (ruling-A pins) was edited: its 9 cross-asker whole-decision equalities went red because the principal is now named per asker. They now compare through apartFromPrincipalPerson, which blanks only the person segment of the principal sentence. This is within the claim's 'their tests' surface, but it is a pin edit the PM should see.",
"label-write: zero writes. The dispatch named no label, and skip-changeset does not apply because the PR publishes and carries a changeset.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude) rather than the harness reminder's model-named Co-Authored-By."
],
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "2 relay strokes, both as objectstack-fleet[bot] (git push x5 is not REST and goes direct): (1) POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create, which became POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/23202/assignees ['zhuangjianguo'], run 38367691682 success, read-back 9430/9430 bytes identical; (2) the os-dev-report comment on #23157 via scripts/pm/post-stamped.mjs",
"open_questions": [],
"out_of_scope_findings": [
"carrier: this family card's PM, to add as rows on #23157 / objectui#12308 class 2 · noted, not filed. Other success copies on sys_approval_request are still bare verbs: approval_reject 'Rejected.' (its inline path says Rejected "TITLE", and the reject route answers the same decide() result, so the same token applies), plus 'Reassigned.', 'Sent back for revision.', 'Information requested.', 'Reminder sent.', 'Recalled.', 'Resubmitted.' and 'Reply posted.'. Read from source only; the toasts were not reproduced in the UI. Dedupe words: approval reject successMessage Rejected bare verb, sys_approval_request successMessage names no request, approvals drawer toast bare verb",
"carrier: 承接者:无 · noted in Acceptance notes, not filed. The principal sentence's ADR-0091 D3 delegated-position clause ('approver from usr_X until ...') and its contributor via ('delegation from usr_X') still name a raw user id (objectui#12308 class 5). Not reproduced live, because the showcase seeds no delegated position. Dedupe words: explain principal delegated position from raw user id, ADR-0091 D3 delegation from id explain"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Claim amendment ·
domain:servicesseat 2 ·session_01FzQy26KznSa5j2gnf3AzsE· 2026-10-13T08:17Z, under claim6140175145. ⛔ Not a new claim. Same branch, same PR #23202;Clause-②: nois unchanged.The seat's review of PR #23202 at
09218ddc1cagainst the dev's report sends one patch round. The rule: a published defect the round touches is fixed in the round. Same-family findings join this family card's rows, never a single-point card.personOf, in the samedetailexpression. The file surface stays the principal layer's sentence. The declaration to [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194 (6140183484) covers it, and the helper and its two reads are declared deviations.successMessagecopies onsys_approval_request. These areapproval_reject"Rejected." and the reassign, send-back, request-info, remind, recall, resubmit and reply actions.request.record_title. The reject route answers the samedecide()result as approve.successMessagevalues insys-approval-request.object.tsand the four translation bundles.explain-asker-gate.test.tsnow compare throughapartFromPrincipalPerson. The record story ruling A governs is still compared in full. Only the principal's person label, read through each asker's own door, is set aside, consistent with ruling A's "by the read access of whoever ASKED".Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23157,
"status": "done",
"branch": "claude/issue-23157-server-authored-ui-text",
"pr": "#23202",
"session": "session_01FzQy26KznSa5j2gnf3AzsE",
"premise_still_valid": true,
"summary": "Patch round (claim amendment 6141342138), at head dd9bb74. (1) The principal layer now names the user an ADR-0091 D3 delegated position came from, through personOf and the asker's door, both in the clause ('approver from Grace Hopper (grace@...; id usr_...) until ...') and in the position's contributor via ('delegation from ...'). Each distinct user is read once. A blind asker gets the bare id. (2) All 8 sibling request actions were measured live on the showcase, each at its route, and every answer carries request.record_title. The measured values: reject EXP-DEMO, reassign EXP-2001, revise and resubmit and recall 'Compliance Audit' (opened by raising a project budget so the revise edge exists), request-info, remind and comment INV-1012. So all 9 copies now name the request: Approve and Reject in the inline wording, the others as Reassigned "T", Sent "T" back for revision, Information requested on "T", Reminder sent for "T", Recalled "T", Resubmitted "T" and Reply posted on "T". Each is updated in step across the four bundles with no new key. The not-changed list is empty, and no route was widened. (3) The changeset is rewritten for the new rows. The new PR body is drafted at /tmp/claude-0/seat/issue-23157/pr-body-r2.md (11441 bytes, Fixes #23157 and the Clause-② line kept, ruling-A pin change stated as seat-accepted). It is NOT written to the PR; see deviations. (4) origin/main moved (to c6139f2) on no file this PR touches, and PR #23179 is still open, so there was no merge.",
"tests": "At HEAD dd9bb74, both full suites ran under os-verify-lock (slot issue-23157), VERDICT command-exit 0. plugin-security vitest: 194 files, 4012 passed, 45 skipped. plugin-approvals vitest: 74 files, 1051 passed. Both typecheck scripts exit 0, and test-typecheck held (plugin-approvals 324 errors / 27 signatures, unchanged). New and updated pins: explain-principal-person.test.ts now has 16 cells; the 4 new D3 delegated-position cells use a real sys_user_position row with delegated_from: an armed check, the directory admin named in clause and attribution, the masked admin with the name but no email, and the blind-admin control with the bare id in both and no name or email anywhere in the decision. approval-service.test.ts has 12 cells, each filling an action's copy from that action's real service answer (decide approve x3, decide reject, reassign, sendBack, requestInfo, remind, recall, resubmit, comment) plus a check that every declared copy has a token. success-copy-names-request-i18n.test.ts (renamed from approve-success-copy-i18n.test.ts) has 4 cells over all nine actions times four locales. New ablations, each committed first, run through scripts/ablation-replace.mjs with a trap, and restored with blob==HEAD and an empty git diff HEAD: C1, delegatorOf back to the raw id: 2 failed and the blind control held, "expected 'Principal Lin Delegate (lin@example.t...' to contain '1 position(s) held via delegation (AD...'". D1, reject back to 'Rejected.': 5 failed, including "expected 'Rejected.' to be 'Rejected \"Acme Renewal\"'". D2, ja-JP recall back to the bare verb: 1 failed, "ja-JP approval_recall.successMessage tokens: expected [] to deeply equal [ '${result.request.record_title}' ]". The round-1 legs A1, A2, B1 and B2 stand as reported. Live: a fresh showcase boot on my own random port, all 8 sibling routes answered unwrapped (keys recorded per route in the PR body draft), then torn down. eslint --no-inline-config over the 10 changed TS files: 10 files, 0 errors, 0 warnings, counted by --format json. The population is eslint.config.mjs's **/*.{ts,...} block, and the narrowing is invariant because no parserOptions.project (type-aware linting) is set.",
"gates": "Re-derived after the diff with dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) at dd9bb74: 66 commands, the same list as round 1. All 66 ran and all exited 0, including check:i18n ('all bundles in sync'), check:i18n-stale-fill, check:nul-bytes, check:engine-double-contract and check:issue-citations. dist was rebuilt for both packages first. --ran reconciliation: '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN', a derived zero with all exit codes recorded. Left to CI: type-check lanes, Test Core, Build Core, dogfood.",
"line_budget": "vs base d4aaeaf: 11 files, +660/-54. This round vs 09218dd: +275/-130. Code: explain-engine.ts +54/-6; sys-approval-request.object.ts +17/-9; 4 bundles +9/-9 each. Tests: +531/-3. Changeset: +22.",
"files_changed": [
".changeset/23157-server-authored-ui-text-names-what-and-who.md",
"packages/plugins/plugin-approvals/src/sys-approval-request.object.ts",
"packages/plugins/plugin-approvals/src/translations/en.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/zh-CN.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/ja-JP.objects.generated.ts",
"packages/plugins/plugin-approvals/src/translations/es-ES.objects.generated.ts",
"packages/plugins/plugin-approvals/src/approval-service.test.ts",
"packages/plugins/plugin-approvals/src/translations/success-copy-names-request-i18n.test.ts",
"packages/plugins/plugin-security/src/explain-engine.ts",
"packages/plugins/plugin-security/src/explain-principal-person.test.ts",
"packages/plugins/plugin-security/src/explain-asker-gate.test.ts"
],
"deviations": [
"CONFLICT, role file over dispatch: the coordinator asked me to update the PR body. os-dev.md says the dev writes the PR body once, at pr_create, and never PATCHes it; later changes go to the seat with the edit named. I did not write the PR body. The full replacement body is at /tmp/claude-0/seat/issue-23157/pr-body-r2.md (11441 bytes, no less-than characters, no control bytes, only closing keyword 'Fixes #23157'). The seat writes it with an issue_patch body on #23202 and reads it back.",
"explain-engine.ts carries the declared helper personOf plus its reads: principal, delegator, and now one read per distinct delegated-position user, collected ahead of layers.push. Both the clause and the contributor via in the same expression changed. isRlsDenySentinel, its docblock and PR #23179's lines are untouched.",
"Test file renamed within this PR: approve-success-copy-i18n.test.ts became success-copy-names-request-i18n.test.ts, because it now pins all nine actions.",
"No merge of origin/main: it moved (to c6139f2, including a version-packages commit), but on no file this PR touches, and PR #23179 is still open.",
"label-write: zero writes, as in round 1 (no label named; the PR publishes, so skip-changeset does not apply).",
"Commit trailers use AGENTS.md's model-free pair rather than the harness reminder's model-named Co-Authored-By."
],
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "This round: 1 relay stroke, the os-dev-report comment on #23157 via scripts/pm/post-stamped.mjs (POST /repos/objectstack-ai/objectstack/dispatches, then POST /repos//issues/23157/comments as objectstack-fleet[bot]). git push x4 is not REST. No PR body write and no label write. Round 1's 2 strokes (pr_create plus assignees, and the first report comment) are unchanged.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Seat ACCEPT: PR #23202 at
dd9bb74c59· round 1 plus one patch rounddomain:servicesseat 2 ·session_01FzQy26KznSa5j2gnf3AzsE· 2026-10-13T09:12Z, under claim6140175145and amendment6141342138. Checked on GitHub and onorigin/main, not from the reports.Checklist
main. Line 1 isFixes #23157and line 2 isClause-②: no — …. The only closing keyword is on server-authored UI text (family card): the approvals drawer's Approve says only "Approved.", and Explain access's Principal resolution prose names the user by raw id #23157. The seat wrote the patch-round body from the dev's text, with line 2 widened from "two strings" to the actual set.plugin-approvalsandplugin-security.check-governed-merges --pr 23202reads it not governed, under the 3000-line threshold. Inexplain-engine.ts, only the principal layer's sentence and its module-privatepersonOfhelper changed, inside the declaration to [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194 (6140183484).isRlsDenySentineland PR feat(spec)!: a controlled_by_parent object derives its master from exactly one required master_detail; the runtime's fallback tiers retire (#23100) #23179's lines are untouched.sys_approval_request: all nine request actions' success copy now names the request through${result.request.record_title}. Each route was measured live and answers with that title, and no route answer was widened. The four bundles were updated in step, with no new translation key; the bundles' generated header permits in-place edits.ql.findOneunder the asker's context), so row-level and field-level security bound what the sentence carries. A blind asker gets the bare id.explain-asker-gate.test.tsnow compares throughapartFromPrincipalPerson. The record story ruling A governs is still compared whole. Only the person label, read through each asker's own door, is set aside. Accepted (amendment6141342138).plugin-security4012 passed,plugin-approvals1051 passed;explain-principal-person16 cells, including masked and blind controls;approval-service12 cells filled from each action's real answer; the i18n tokens test covers 9 actions × 4 locales;plugin-approvalsandplugin-securitypatch. The success copy and the explain prose change, and no accept set or published shape moves.dd9bb74c59(latest run per name): 40 names, 34 success, 6 skipped, 0 red. The PR-body edit re-fires the PR-event checks, and the queue waits for them.mcp_calls0.Out-of-scope findings: none left. The round-1 findings (the other bare-verb copies, and the delegated-position clause) were folded into this PR by the amendment.
Landing:
pr_ready+automerge_enablethrough the relay. This seat follows it to MERGED.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed · PR #23202 merged through the merge queue at 2026-10-13T09:39Z as
920bc12a24, and that commit is onorigin/main(read 2026-10-13T09:44Z).domain:servicesseat 2 ·session_01FzQy26KznSa5j2gnf3AzsE, under claim6140175145and amendment6141342138, ACCEPT6142261622.main:sys_approval_requestname the request in their success copy, through${result.request.record_title}, in four locales with no new key.ready_for_review, thenauto_merge_enabled, thenadded_to_merge_queueat 2026-10-13T09:14Z. The PR's only closing keyword wasFixes #23157, and no other card closed in the window.completedby that keyword. This write clearspm:dispatchedand the assignee;priority:p3,domain:servicesandarea:workflowstay.domain:uilane's. Nothing here waits on them..changeset/23157-server-authored-ui-text-names-what-and-who.md(patch) ships at the next version release. A merge tomaindoes not publish.Generated by Claude Code