Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .changeset/dispatcher-plugin-error-code-doc-drift.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
---
"@objectstack/runtime": patch
---

docs(runtime): `errorResponseBase` 文件头不再把 `details.code` 说成线上位置 (#6270)

**纯注释改动,零行为变更。** 没有一行运行时代码被触碰,线上响应体与本次改动前逐字节相同。修的是
`packages/runtime/src/dispatcher-plugin.ts` 文件头里一句会误导下一个改这个函数的人的话。

原文说:`details.code`(#3842, below)carries `READ_SCOPE_COMPILE_FAILED` **to the
client** untouched,so **what a machine reads** is unchanged。这两个断言讲的都是**线上位置**,
而 #5811 之后线上位置不是 `details.code`:`errorResponseBase` 只是把 code **暂存**进一个本地
`details` 对象,`buildApiError` 随即跑 `splitSemanticCode`,把它**提升**进 `ApiErrorSchema`
声明的 `error.code` 字段,并把已经空掉的 `details` 返回为 `undefined` —— 于是 `details` 键整个
从 body 中消失,`error.details.code` 根本不存在可读。实测 500 body 就是:

`{"success":false,"error":{"code":"READ_SCOPE_COMPILE_FAILED","message":"Internal server error","httpStatus":500}}`

这是同一句话漂移的**第四处**。#6123(PR #6264)修了前三处,并明文把 `dispatcher-plugin.ts` 划为
⛔ 只读参考面,所以第四处按 PD #10 单独立单。措辞刻意与 #6264 在另外三处落的保持一致 —— 这里的价值是
四处同一句话,而不是第四种独立说法。

危害方向比前三处更陡:前三处的受害者是读 CHANGELOG / 读 `service-analytics` 的人,这一处的受害者
**就站在做暂存的那个函数里**,读到「details.code carries it to the client」会直接把**本地变量名**
当成**线上契约**。注释里因此明写了一句 `Do not mistake the local variable name for the wire contract`。

顺带把同一 doc block 里 `:452`-`:457` 那段 JSON 示例的时态钉明确:它展示的是 #5811 **修复之前**的
泄漏形态(message 原文直接落在 body 里),与新补的当前 body 并排读极易混淆。现在 fence 顶部有一行
`⚠️ PAST TENSE` 标注并指向 doc block 末尾的当前形态。示例本身**没有删** —— 它记录的是「我们曾经
泄漏过什么」,是这条 `[#5811]` 条目在做的正经工作。

新措辞的真实性由**既有**测试锚定,本次没有新写断言:
`packages/runtime/src/analytics-query-read-scope-withhold.test.ts:218` 用真 `AnalyticsService`
+真挂载路由断言 `expect(res.body.error.code).toBe('READ_SCOPE_COMPILE_FAILED')`。
27 changes: 24 additions & 3 deletions packages/runtime/src/dispatcher-plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -450,6 +450,8 @@ function sendResultBase(
* the RLS POLICY the tenant is being filtered by:
*
* ```
* ⚠️ PAST TENSE — the leak this entry CLOSED, not what this exit answers today.
* For the current body see the corrected shape at the end of this block.
* POST /analytics/query (tenant caller, object with a broken sharing rule)
* → 500 {"success":false,"error":{"message":"[read-scope-sql] unsafe field
* identifier \"secret_policy_field\" — refusing to build read scope
Expand All@@ -464,9 +466,28 @@ function sendResultBase(
* boundaries. ⛔ It is NOT "withhold every 5xx" — #5667 kept UNDECLARED 5xx
* legible on purpose, and a bare `Error` still goes through the heuristic alone.
*
* The code still travels: `details.code` (#3842, below) carries
* `READ_SCOPE_COMPILE_FAILED` to the client untouched, so what a machine reads is
* unchanged and only the prose is withheld — into `errorReporter` and the log.
* The code still travels, and `READ_SCOPE_COMPILE_FAILED` reaches the client
* untouched — so what a machine reads is unchanged and only the prose is withheld,
* into `errorReporter` and the log.
*
* ⚠️ It reaches the client at `error.code` — NOT `error.details.code`, which is
* where this note pointed until #6270 corrected it (#6123 corrected the same
* sentence at three sibling sites). The `details` assembly below (#3842) only
* STAGES the code in a local object; `buildApiError` then runs
* `splitSemanticCode` (`./error-envelope.ts`), which PROMOTES it into the declared
* `ApiErrorSchema` field and returns the now-empty `details` as `undefined` — so
* the key is omitted from the body and `error.details.code` is never present to
* read. Do not mistake the local variable name for the wire contract. The measured
* 500 body is exactly:
*
* ```json
* {"success":false,"error":{"code":"READ_SCOPE_COMPILE_FAILED",
* "message":"Internal server error","httpStatus":500}}
* ```
*
* Pinned end-to-end in `analytics-query-read-scope-withhold.test.ts`, which
* asserts the code at `error.code` against a real `AnalyticsService` on a real
* mounted route.
*/
function errorResponseBase(err: any, res: any, securityHeaders?: Record<string, string>): void {
const validation = validationFailureDetails(err);
Expand Down
Loading