Repository navigation
fix(rest): relay a declared service outage from the external-datasource routes as 503 - #30952
Conversation
…ce catch as 503 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cwJiP1ALmQ1rTttNGc
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cwJiP1ALmQ1rTttNGc
📓 Docs Drift CheckThis PR changes 1 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d52ba3bb819efdd2b6068e0e38e14c2026f026bd && git checkout d52ba3bb819efdd2b6068e0e38e14c2026f026bd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6ddf827236902c0ccaee56b65431a4c8a9c7ac09 3bccb0f74ef29131c5e5fdc3c354bd05f64873a8 && git checkout -B drift-repro 6ddf827236902c0ccaee56b65431a4c8a9c7ac09 && git merge --no-ff 3bccb0f74ef29131c5e5fdc3c354bd05f64873a8
node scripts/docs-audit/affected-docs.mjs --json 6ddf827236902c0ccaee56b65431a4c8a9c7ac09
|
|
domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7 席位核对 · ACCEPTPR #30952 · head 维护者速读(席位校正)The body's six lines stand as written, read against the diff. 席位核对
|
|
domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7 CI red · not this PR's · 2026-11-06T14:17Z
|
Requested by Elon · project thread
Fixes #30609
Clause-②: no
Metadata-protocol: none
domain:server#5 card thread · session
session_016WV4cwJiP1ALmQ1rTttNGc· branchclaude/project-thread-ticta6(named in the seat's re-posted claim on the card). Branched frommainat 33043bd.维护者速读
POST /api/v1/datasources/:name/external/tables/:remote/import)遇到租户服务暂时不可用时,现在回503 SERVICE_UNAVAILABLE(请稍后重试),不再回400 EXTERNAL_IMPORT_ERROR(请求写错了)。同一组外部数据源接口共用这一处,所以它们遇到同类服务停摆也一样回 503。import_object工具早就回 503,REST 这一处没跟上。status 503和code SERVICE_UNAVAILABLE的错误;其它拒绝照旧 400,此前此后都不会保存任何东西。回滚即 revert 本 PR。packages/rest/src/external-datasource-routes.ts的answerThrown(新增的 503 分支,在 404 分支之前);packages/rest/src/external-datasource-unknown-name.test.ts末尾新增的两个用例。pnpm --filter @objectstack/rest exec vitest run src/external-datasource-unknown-name.test.ts,预期 17 个用例全过,其中「answers 503 SERVICE_UNAVAILABLE and saves nothing」一条。What changes
answerThrown, the one catch every/datasources/:name/external/*route answers through, relayed only sandbox faults and the404 RESOURCE_NOT_FOUNDenvelope; every other throw became this family's400. Since PR #30602 the import's landing reads the effective tenancy posture (classifyAdmissionTenancyPosture), which throwsAuthzStoreUnavailableError(status 503,code SERVICE_UNAVAILABLE) when a registeredtenancyservice cannot answer. The import therefore told the caller its request was malformed when the truth was "retry".It now relays an error carrying
status 503andcode SERVICE_UNAVAILABLEas that envelope, ahead of the404relay, exactly asservice-datasource'sbadRequestdoes.status+code), notisAuthzStoreUnavailableError. It is the read both twins make (badRequestinadmin-routes.ts, and the MCPimport_objecttool'sthrownRefusalEnvelope), so the three doors cannot drift on what counts as an outage. The brand predicate would relay only the authorization-store outage and keep answering400for any other service that declares the same envelope. Both fields are required, so an error carrying a straystatuscannot re-route itself.EXTERNAL_DATASOURCE_ERRORroutes get the same relay. They share the catch, and a declared dependency outage is the same truth on a read or a draft as on the import:400would tell the caller to change a request that was never wrong. This matchesbadRequest, which serves every admin route the same way. No refusal that is not a declared outage changes: a sandboxed refusal declaring a 4xx and the metadata door's409 DESTRUCTIVE_CHANGEkeep this family's400and code.File surface as claimed:
answerThrownonly, its test beside it, one changeset.refuseFederationRequestand the route registrations are untouched (open draft PR #30702 edits those; hunks disjoint, re-checked before push).Tests
New top-level
describeinpackages/rest/src/external-datasource-unknown-name.test.ts, the file that already pinsanswerThrown's404relay over the realExternalDatasourceServiceand the realHonoHttpServer. Itsmount()takes config overrides now; the existing pins are unchanged. The landing hook runs the realclassifyAdmissionTenancyPostureover atenancyfactory that rejects, which is the readservice-datasource's plugin makes.503 SERVICE_UNAVAILABLE, its message names the failedtenancyread, carries nodata, andpersistObjectis never called;400 EXTERNAL_IMPORT_ERRORwith its own sentence, nothing saved.Reproduction and ablation, one run (
node scripts/ablation-replace.mjs, anchorenvelope?.status === 503 && envelope?.code === 'SERVICE_UNAVAILABLE'replaced byfalse, anchor x1 -> x0, blob b99785dd -> 6d0515af): the outage pin goes red withexpected { status: 400, … } to deeply equal { status: 503, … }(1 failed, 16 passed), which is the defect asmainanswers it; restored, blob == HEAD,git diff HEADempty. The test imports the routes fromsrc/by relative path, so no build sits between the mutation and the run.Acceptance notes
Verification
All local readings on head
3bccb0f74e(the final head), worktree offmain33043bd;origin/mainhas since moved 6 commits, none touchingpackages/rest,service-datasourceorpackages/core/src/security, so not merged.Derived gate families: 60 derived, 59 run green, 1 NOT MEASURED
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 60 commands; each run with its exit code captured before any pipe (record:gates-ran-3bccb0f74e.txt).dispatch-gates.mjs --ran:60 derived famil(ies) accounted for — 59 run, 1 NOT-MEASURED.pnpm check:dual-build-cjs-loads, reason: exit 3PREREQUISITE NOT MET(reads the whole tree'sdist/, which this worktree has not built; CI's run is the reading).Package and repo checks
pnpm --filter @objectstack/rest test(under the verify lock): exit 0,Test Files 393 passed (393),Tests 7458 passed | 332 skipped (7790).pnpm --filter @objectstack/rest typecheck: exit 0.pnpm lint: exit 0.pnpm check:cli-command-ids: exit 0.pnpm check:adr-symbol-anchors: exit 0.test:repo(therepovitest project) not run locally: the diff touches no file in it and the derivation does not name it; CI'sTest Coreruns it.!in the title and no(narrowing)arm.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4cwJiP1ALmQ1rTttNGc