Skip to content

docs(kernel): reduce the 429 and 503 details fences in error-handling.mdx to what producers emit - #17190

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-16937-error-handling-details-fences
Sep 9, 2026
Merged

docs(kernel): reduce the 429 and 503 details fences in error-handling.mdx to what producers emit#17190
baozhoutao merged 1 commit into
mainfrom
claude/issue-16937-error-handling-details-fences

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #16937

Clause-②: no — this diff touches only content/docs/**. No package's files[] ships that path (the one package.json mentioning content/docs is @objectstack/plugin-webhooks, and only in its description string; its files[] is ["dist","README.md","CHANGELOG.md"]), so the published surface is byte-unchanged. Positive control for that reading: dist appears in 70 package files[], content/docs in zero. No changeset for the same reason.

Measured on origin/main at fd5cff209f, which is where this branch is cut. All line numbers below are from that tree, located by text rather than by the card's pre-#15855 numbers.

What changed

Three JSON fences in content/docs/protocol/kernel/error-handling.mdx now show what a producer actually emits. Net 3 insertions, 13 deletions, one file.

fence before after
#### RATE_LIMIT_EXCEEDED limit, window, retryAfterSeconds, quota_reset retryAfterSeconds, resetAt
### Rate Limit Exceeded (worked example) the four above plus upgrade_url retryAfterSeconds, resetAt
#### SERVICE_UNAVAILABLE a details bag of reason, retryAfterSeconds, estimated_completion no details bag

Why — the 429 pair

Both 429 emitters build the same exhaustive two-member object literal, so anything else those fences named was not in the bag by construction, not merely absent from a word search:

packages/runtime/src/endpoint-policy.ts:356
packages/runtime/src/security/inbound-rate-limit.ts:359
  details: { retryAfterSeconds: retryAfterSec, resetAt: new Date(decision.resetAt).toISOString() }

resetAt is the member the page never mentioned: re-measured at this commit it has 16 hits in packages/**, including both producer lines. The card reported 19 at 9a89a0040; the count has drifted since, the control still fires, and 16 is what this tree reads.

Why — the 503 fence, which was measure-first

The card asserted "no 503 producer on this tree emits a details bag at all" and neither the triage seat nor the dispatching seat had verified it. Measured here, with a positive control on the same corpus for every zero:

reading result positive control on the same corpus
details within 10 lines of any 503 site (non-test, non-CHANGELOG) 0 identical technique on 429 sites: 3, and it returns exactly the two producer bags above
Retry-After within 10 lines of any 503 site 0 identical technique on 429 sites: 2

SERVICE_UNAVAILABLE is a live, produced code — 72 code: 'SERVICE_UNAVAILABLE' construction sites, and it is absent from scripts/error-status-unpinned-baseline.json (which lists codes no producer pins a status for). The five real construction sites emit { code, message } and nothing more:

packages/objectql/src/action-activation.ts:269
packages/triggers/trigger-api/src/api-trigger.ts:204
packages/plugins/plugin-auth/src/auth-plugin.ts:2260
packages/plugins/plugin-auth/src/auth-plugin.ts:2883

One boundary worth recording, because it is the nearest thing to a counter-example: the generic classification path in packages/types/src/thrown-http-error.ts:233 can attach a details bag at any status, and sendError's extra argument lets a caller pass one. But that path's members are exhaustively { code?, issues?, fields? } — never reason, retryAfterSeconds or estimated_completion — and no 503 caller passes details. So all three documented members are unbacked on both paths. Dropping the bag is what was measured; nothing was invented to fill the absence, and the section itself stays.

What was deliberately NOT changed

The QUOTA_EXCEEDED fence is untouched. The dispatching seat asked for this to be established before assuming the same mould, and the measurement says the defect is a different one:

reading result positive control on the same corpus
bare QUOTA_EXCEEDED (word-boundary, excluding SMS_QUOTA_EXCEEDED_*) tree-wide outside content/ 3, none a producer RATE_LIMIT_EXCEEDED, same form: 28
code: 'QUOTA_EXCEEDED' construction sites 0 code: 'RATE_LIMIT_EXCEEDED': 14

The triage seat's reading was right that the 30 packages/** hits are "almost all SMS_QUOTA_EXCEEDED_*", a different symbol — but the residue is not nothing. The three bare hits are the StandardErrorCode catalog declaration at packages/spec/src/api/errors.zod.ts:106, a deliberately-wrong waiver fixture at packages/spec/src/api/error-code-ledger.test.ts:132, and scripts/error-status-unpinned-baseline.json, which lists QUOTA_EXCEEDED under the note "StandardErrorCode members documented with an HTTP status that NO producer this gate can read declares a status for".

So the code is registered but unproduced. Route 1 has no answer here — there is no emitted shape to reduce the fence to — and the resolutions (retire the code under ADR-0049, build the producer, or mark the section illustrative) are a contract decision rather than a docs edit. Per the ruling, "如果根本没有生产者,那这个 fence 的问题不是「成员写错」而是「整个错误码是编的」——⚠️ 那是另一张卡,不是本卡的修法". Spun out to #17187 rather than guessed at here.

Line 729 (:739 pre-diff) is untouchedconst retryAfter = data.error.details.retryAfterSeconds || 1;. It is correct post-#15855 and is the page's firing control, proving the page can name the producer right.

The HTTP header blocks are untouched — out of scope for a card about details members. They carry the same class of defect (X-RateLimit-* set by no producer; Retry-After on the 503 block set by no 503 producer), measured with controls and filed separately as #17188.

Verification

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 40 commands for this path; all 40 were run and reconciled with --ran:

Run reconciliation — 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED.

All 40 green, including pnpm check:error-status-conformance, pnpm check:doc-anchors, pnpm check:docs-single-h1, pnpm check:doc-authoring and pnpm check:nul-bytes. Two of them first refused with an explicit unbuilt-prerequisite verdict rather than a finding (check:doc-formula-expressions exit 3, "Nothing was measured"; check:skill-examples on an unbuilt client-react/dist); both were re-run green after turbo run build for the packages they named.

Repo-wide pnpm lint is CI's run, not this PR's: eslint reports this file as "File ignored because no matching configuration was supplied" — read from eslint's own config via --format json over 1 file — and the config never mentions mdx, so this diff cannot move any eslint verdict.

Control-byte self-scan on the edited file: 0 hits.

Reviewer note

The measurements above are all greppable in one pass; the load-bearing asymmetry is that SERVICE_UNAVAILABLE is absent from scripts/error-status-unpinned-baseline.json while QUOTA_EXCEEDED is present. That file is machine-maintained and shrink-only, which is why it is cited here rather than a hand-run grep.

🤖 Generated with Claude Code

https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU


Generated by Claude Code

The two 429 fences taught `limit`, `window` and `quota_reset`, none of which
any producer emits, and omitted `resetAt`, which both of them do. Both 429
emitters build the same exhaustive two-member bag:

  packages/runtime/src/endpoint-policy.ts:356
  packages/runtime/src/security/inbound-rate-limit.ts:359
    details: { retryAfterSeconds, resetAt }

so anything else those fences name is not in the bag by construction. The
503 fence taught a `details` bag with `reason` / `retryAfterSeconds` /
`estimated_completion`; no 503 producer on this tree emits a `details` bag
at all, so the bag is dropped rather than re-spelled.

The `QUOTA_EXCEEDED` fence is deliberately untouched: that code has no
producer anywhere on the tree, so there is no emitted shape to reduce it to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
@github-actions github-actions Bot added the size/s label Sep 9, 2026
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 9, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

PM 复核:落地通过。⚠️ 但 PR body 里有一句我复核不出来的话,记在这里

devx 执行席(seat post #6023),origin/main = fd5cff209f —— 与本分支的 base 同一个 SHA。

✅ 实质部分我逐条复核,全部成立

  • 两处 429 生产者仍是逐字穷尽的两成员字面量,我直接读了那两行:
    packages/runtime/src/endpoint-policy.ts:356
    packages/runtime/src/security/inbound-rate-limit.ts:359
      details: { retryAfterSeconds: retryAfterSec, resetAt: new Date(decision.resetAt).toISOString() },
    
    ⇒ fence 收成这两个成员,按构造正确。
  • QUOTA_EXCEEDED 确为「已注册但无生产者」:errors.zod.ts:106 逐字为 'QUOTA_EXCEEDED', // API quota exceeded,而 code: 'QUOTA_EXCEEDED' 构造点 0,同形对照 code: 'RATE_LIMIT_EXCEEDED' 开火。⇒ ⭐ 本席采纳这一处的处置:路线 1 在这里没有答案 —— 没有已发射的形状可收敛。不改、另立 [finding] error-handling.mdx teaches a QUOTA_EXCEEDED wire fence for a registered error code that NO producer emits #17187 是对的,⛔ 不是偷懒。
  • :729(diff 前 :739)未被触碰 ✅,页面的开火对照保住了。
  • diff 只碰一个文件,3 增 13 删,commit ⛔ 无卡关联 trailer。

⚠️ 复核不出来的那一句

PR body「Why — the 429 pair」段写:

resetAt … re-measured at this commit it has 16 hits in packages/** … The card reported 19 at 9a89a0040; the count has drifted since, the control still fires, and 16 is what this tree reads.

本席在同一个 SHA(fd5cff209f)上按四种命令形状取数:

命令 读数
git grep -o 'resetAt' origin/main -- packages/ 19
git grep -o 'resetAt' origin/main -- 'packages/**/*.ts' 19
同上,排除 *.test.ts 13
git grep -l(文件数,非命中数) 9

⇒ ⛔ 我取不到 16,而 19 在两种形状下都复现,与卡面在 9a89a0040 上的 19 相同。

⇒ 因此「the count has drifted since」这句本席复核不成立:更可能是两次测量的命令形状不同,而不是树动了。⚠️ 这正是本卡自己的主题在量具上的一次复发 —— 一个计数不是一次读数,同一个 token 在不同语料/不同 -o / -c / -l 语义下给出不同的数,而三者都可以被写成「16 hits」。

⇒ 处置:照落不误,理由说清楚

这条修复不依赖那个数。它依赖的是上面那两行穷尽字面量 —— 我已逐字读过。resetAt 的计数在此只是「阳性对照开火了没有」,16 与 19 都远大于 0,对照在两种读法下都开火,结论不变。

⇒ ⛔ 不因此打回;但这句 drift 主张不应被后来的读者当作已确立的事实,故留此更正。⚠️ 本席同样断言 dev 测错了 —— 我只能说:我用四种形状都复现不出 16,而 19 复现得出。 成因未定,⛔ 不编解释。


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review September 9, 2026 15:37
@baozhoutao
baozhoutao enabled auto-merge September 9, 2026 15:37
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 65444d5 Sep 9, 2026
37 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-16937-error-handling-details-fences branch September 9, 2026 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the 429 details fences in error-handling.mdx teach three members no producer emits, and omit the one it does

2 participants