Repository navigation
docs(spec): server.trustProxy describe says "two consumers" but the per-endpoint rate limiter is a third #25058
Description
Filing gate ① a measured defect (a documentation inaccuracy), found by the retroactive contract review of PR #24859 (record 6308841043, merge 55d7aaba4c, closes #24818). Filed by the domain:spec seat 3 (session session_01D6F9axfoPzR7t8G1J7Ytzz). ⛔ Not a claim.
The finding
The server.trustProxy .describe() text in packages/spec/src/system/stack-server.zod.ts (and so the generated row in content/docs/references/system/stack-server.mdx) now reads "One client-address rule, two consumers: the inbound rate limiter when server.security.rateLimit.enabled is set, and the authentication plugin …".
On origin/main there is a third caller of the same rule:
packages/runtime/src/endpoint-policy.ts:366-372callsresolveRateLimitKey({… trustProxy: input.trustProxy === true })for any endpoint that declaresrateLimit(packages/spec/src/api/endpoint.zod.ts:189).packages/runtime/src/dispatcher-plugin.ts:2278-2282passesconfig.rateLimit?.trustProxyinto that chain. The registry is built unconditionally (dispatcher-plugin.ts:2115), so it does not depend onserver.security.rateLimit.enabled.
The same file's own header (stack-server.zod.ts:56) already names "the endpoint policy chain". No key, default or behaviour is misstated. Only the count is wrong, and the "when … enabled is set" qualifier predates #24859.
Candidate shape (for triage)
Name the endpoint-policy chain as a consumer, or drop the count, and regenerate the reference with gen:schema && gen:docs. @objectstack/spec patch, Clause-②: no.
Dedupe
Method: every objectstack-ai/objectstack issue updated since 2026-09-01 (any state), from the repo-scoped issues?state=all endpoint, matched on trustProxy in the title or body.
- Parent, closed: spec + ADR-0069 D5: server.trustProxy's describe names only the rate limiter, and ADR-0069 D5's mechanism cell names the old header list; since PR #24795 the auth IP allow-list and the login audit consume the same rule #24818.
- Related, closed: security: the auth IP allow-list does not resolve the client IP the way the runtime's trust-proxy rule does, and admits an undetermined IP (detail withheld under the disclosure ruling) #24739 and plugin-auth: the vendored auth library's own client-address reader does not follow server.trustProxy, so the addresses it records and keys on are not the ones the server established (detail withheld under the disclosure ruling) #24799 (both about the client-address rule).
- Related, open: docs: HARDENING.md and production-readiness.mdx still describe rate limiting as adapter-wired and off, and never name server.security.rateLimit or trustProxy #24740 (
HARDENING.mdandproduction-readiness.mdxon rate limiting). Those are other files, and this card does not overlap it. - New.
Activity
objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 35
Session:
session_01D6F9axfoPzR7t8G1J7YtzzAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25058-trustproxy-consumers-countWorktree:
objectstack-issue-25058Domain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/main4f97aeb5c4or later; stop on a breach and explain it in the report):packages/spec/src/system/stack-server.zod.ts: theserver.trustProxy.describe()/ TSDoc text only;content/docs/references/system/stack-server.mdx;.changeset/25058-*.md.Container & model:
S,mode:subagent,model: default (no path-derived mandate)Clause-②: no (prose only: no shape, key, default or accepted value moves)
Responsibility:
the follow-up the retroactive contract review of PR #24859 (6308841043) filedThread-read: none
Serial constraints cleared: no in-flight PR of this seat touches
stack-server.zod.ts. The dev re-checks open PRs before opening its own.Read on
origin/main4f97aeb5c4at 2026-10-21T10:10Z: thetrustProxydescribe says "two consumers", and the per-endpoint declarative rate limiter (runtime/src/endpoint-policy.ts) is a third.objectstack-fleet commentedon Oct 21, 2026
Paused — claim released (handover)
The seat stopped this dispatch at the maintainer's request (wind-down). The claim
6313443713ends here, and the card returns topm:queue.claude/issue-25058-trustproxy-consumers-count, 1 commit, pushed at177ffab832("server.trustProxy names the endpoint policy chain as a third …"), clean. No PR is open.stack-server.mdx. The next dev picks up from the branch, regenerates, verifies and opens the draft PR. The PR owes a contract review, because it changes text inpackages/spec/src/**.objectstack-fleet commentedon Oct 21, 2026
Resumed — claim restored
The handover note above is withdrawn. The maintainer has asked the seat to carry every half-finished card through to merge, so this seat's claim on this card stands again, and
pm:dispatchedis restored. The seat now runs one dev at a time. This card resumes from its pushed branch head, in the seat's serial order.objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{ "issue": 25058, "status": "done", "branch": "claude/issue-25058-trustproxy-consumers-count", "pr": "https://github.com/objectstack-ai/objectstack/pull/25116", "session": "session_01D6F9axfoPzR7t8G1J7Ytzz", "premise_still_valid": true, "summary": "Resumed from 177ffab832. The server.trustProxy describe and TSDoc now name three consumers of the client-address rule. I checked each on origin/main ba244f1b48. (1) The inbound rate limiter: runtime/src/security/inbound-rate-limit.ts:156-159, called via resolveRateLimitKey :306-310 and installed at dispatcher-plugin.ts:906-908; it only exists when a server-level budget is enabled. (2) The endpoint policy chain: endpoint-policy.ts:366-372, gated on the endpoint's own enabled rateLimit (deriveBucketConfig :162-174), threaded at dispatcher-plugin.ts:2278-2282 from a registry built unconditionally at :2115. (3) AuthPlugin: auth-manager.ts:10059-10066 and auth-plugin.ts:2777-2784, given the value at cli serve.ts:4263. A whole-tree non-test git grep for resolveClientAddress, resolveRateLimitKey and trustProxy finds no fourth caller. Both qualifiers are correct. I merged origin/main (9424cb264b), regenerated content/docs/references/system/stack-server.mdx with gen:docs (9aa6fdc4c8), and opened draft PR #25116 through the relay, assigned to baozhoutao. Its body starts with 'Closes #25058' and carries the claim's Clause-② line. The changeset (@objectstack/spec patch, the same Clause-② line) was already in 177ffab832. No open PR touches either stack-server file: I paged the file lists of all 9 open PRs. Deviation: the harness's commit trailer named a model, the pre-push check-commit-card-trailers refused it, and I reworded the unpublished commit to the model-free pair before pushing, so no history was rewritten. I opened the PR with the dispatch's 'Closes' keyword; os-dev.md's text says 'Fixes', and both close the card.", "tests": "At head 9aa6fdc4c8, all through os-verify-lock where heavy: `pnpm --filter @objectstack/spec build` gave VERDICT command-exit 0. `pnpm --filter @objectstack/spec test` passed 706 files (21459 tests, 1 todo), command-exit 0. `pnpm --filter @objectstack/spec test:repo` passed 72 files (1237 tests), command-exit 0. `pnpm --filter @objectstack/spec typecheck` gave command-exit 0, including check:test-typecheck OK. `check:generated` first reported '1 of 14 artifact(s) stale: content/docs/references/**'; after gen:docs it exited 0 with 'All 14 generated artifacts are up to date'. `dispatch-gates --commands` derived 105 families and I ran all 105, capturing each exit before any pipe. Six first exited 3 (PREREQUISITE NOT MET). After a locked `turbo build --concurrency=2` of the lint, formula, client-react and objectql closures, five of them exited 0 on re-run: check:doc-formula-expressions, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift and check:lean-entry-closure. That leaves one NOT MEASURED: check:dual-build-cjs-loads, reason: it needs every workspace package's dist, a full build was not run locally, and CI owns it. `dispatch-gates --ran` with exit codes: '105 derived famil(ies) accounted for — 104 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. The changeset gates check-empty-changeset, check-changeset-no-major, check-adr-0087-registration and check:changeset-gate-self-tests, and check:nul-bytes, all exited 0. No ablation (prose-only change). No lint run (CI-owned).", "mcp_calls": "0", "api_writes": "2 — (1) relay pr_create via POST /repos/objectstack-ai/objectstack/dispatches (run 39603600654: POST /pulls plus POST /issues/25116/assignees, read back byte-identical); (2) this os-dev-report comment via POST /issues/25058/comments. Plus git push of the branch (not a REST write). Zero label writes: the dispatch named no label, and skip-changeset does not apply because the spec describe ships.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted in PR #25116 Acceptance notes, not filed · docs/adr/0069-enterprise-authentication-hardening.md D5 amendment (~line 145) still implies two consumers ('imported back by the runtime's inbound rate limiter, so both apply it identically'). It is not edited because docs/adr/** is governed (Tier H). Dedupe words: trustProxy, ADR-0069 D5, client-address consumers, endpoint policy chain" ] }Generated by Claude Code