fix(service-datasource,rest,spec)!: external-datasource refusals answer their own error code (#4249) - #4263
Merged
Conversation
…er their own error code (#4249) The three admin routes that dispatch to external-datasource answered their 400s with DATASOURCE_ADMIN_ERROR — the code the ADR-0112 ledger registers as a refusal from the OTHER service. Same mis-attribution #4225/#4234 fixed in the 503 message, one field over, and machine-readable this time. - Register EXTERNAL_DATASOURCE_ERROR in the error-code ledger under both emitting packages (per-package rows are provenance, not identity). - admin-routes.ts: badRequest takes the same ServiceName the route passed to resolve, and maps it to the registered code — the 400 code now comes from the service the route actually dispatches to, like the 503 message. - external-datasource-routes.ts: the two introspection routes (GET /tables, POST /tables/:remote/draft) had no catch, so the same service operations that answer 400 through the admin surface surfaced there as the adapter's non-envelope 500. They now answer 400 EXTERNAL_DATASOURCE_ERROR too — one operation, one failure contract, on both paths. - Conformance rows move with the fix; a new per-route REFUSALS table pins the 400 code to the service the way #4234's table pins the 503 message. No consumer branches on the old code on those routes (grepped objectstack and objectui in all the ADR-0112 sweep forms). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Di9hefxJMFD3rxiP2G2Eug
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 108 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…in-error-code-y1eels
Resolves the #3973/#4229 overlap: the per-module sendOk/sendError pairs this branch had annotated were consolidated into @objectstack/types on main, so the local definitions go and the #4249 notes move to the module docs. With the shared sendError typing `code` as the closed ErrorCode union, SERVICE_ERROR_CODE is now Record<ServiceName, ErrorCode> — the mis-attribution class this branch fixes is a compile error going forward. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Di9hefxJMFD3rxiP2G2Eug
os-zhuang
added a commit
that referenced
this pull request
Jul 31, 2026
…tes answer their registered refusal code (#4264) (#4313) #4249 (#4263) gave the rest surface's two introspection routes a failure contract; three sibling routes still had no catch around their service call, so a throw was swallowed by the adapter and surfaced as the pre-#3675 non-envelope 500 { error: 'No response from handler' } — no success flag, no error.message, the real cause lost. Each now answers 400 in the declared envelope, under the code registered (ADR-0112) for the service the route dispatches to: - GET /api/v1/datasources → DATASOURCE_ADMIN_ERROR, matching its eight siblings in admin-routes.ts - POST /datasources/:name/external/refresh-catalog and POST /datasources/:name/external/validate → EXTERNAL_DATASOURCE_ERROR, the code #4249 gave the two introspection routes above them The issue left INTERNAL_ERROR open as an alternative; the per-service codes win on consistency — every other catch in both modules, including pure reads, already answers 400 with the service-attributed code, and refreshCatalog's dominant throw class is the one #4249 already adjudicated as a 400 refusal on listRemoteTables. A 500 would fork the failure contract within a module — the drift #4249 removed. No new codes (both registered by #4263). The envelope-conformance suites and the REFUSALS pin table gain one row per route. Closes #4264 Claude-Session: https://claude.ai/code/session_01VDgGWS97x6vuikjmgMswtk Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4249。
采用的方案:issue 选项 1 —— 注册
EXTERNAL_DATASOURCE_ERROR三个候选里选了注册新码,理由与另外两个的排除法一致:
EXTERNAL_IMPORT_ERROR):introspection 不是 import,名字撒谎;改名对已有消费方是无收益的破坏。plugin-hono-server/src/adapter.ts)会把 handler 的 rejection 吞掉并回500 { error: 'No response from handler' }—— 正是 service-i18n error responses are bare{ error }, not the declared{ success: false, error: {...} }envelope #3675 之前的非声明信封形态,连error.message都没有。这不是可以"settle"进契约的行为。ErrorCode闭合联合约束的服务专属 400 不成立。归属包的决定(issue 里点名要决定的):按账本自己的规则 —— "A code emitted by several packages is listed once per emitting package — the union dedupes; the per-package rows are provenance, not identity" —— 同时登记在两个发射方
@objectstack/service-datasource与@objectstack/rest名下,不需要选"唯一 owner"。改动
packages/spec/src/api/error-code-ledger.zod.ts:注册EXTERNAL_DATASOURCE_ERROR(introspection/connection-test refusal from the external-datasource service),两个发射包各一行。service-datasource/admin-routes.ts:badRequest不再硬编码DATASOURCE_ADMIN_ERROR,改为接收路由传给resolve的同一个ServiceName,经SERVICE_ERROR_CODE映射出注册码 —— 与 fix(service-datasource): the datasource-admin 503 names the service the route actually needs (#4225) #4234 对 503message的处理同构:归因来自路由实际分派的服务。三条 external 路由(GET /:name/remote-tables、POST /:name/test、POST /:name/object-draft)的 400error.code由此变为EXTERNAL_DATASOURCE_ERROR;六条 admin 路由字节不变。rest/external-datasource-routes.ts:issue 指出的"同两个服务操作、两条路径、两种失败契约"就此收敛 ——GET /external/tables与POST /external/tables/:remote/draft此前完全没有catch(服务一抛就是上面那个非信封 500),现在与 admin 路径一致地回400 EXTERNAL_DATASOURCE_ERROR。import 路由的EXTERNAL_IMPORT_ERROR保持不变。admin-routes.test.ts新增 The same three datasource-admin routes answer 400 with the wrong service's error code (#4225 follow-up, one field over) #4249 的按路由REFUSALS表 —— 每行只接线该路由应解析的服务(接错服务会先 503 而非 400),code列是字面 pin,不从映射推导;rest侧一致性套件补上两条新 400 的行。conformance 套件按ErrorCode闭合联合做 schema 校验,所以未注册码在这里本身就过不去。.changeset:三包 patch(fixed group),含线上可见变化的完整清单。线上可见变化
error.codeDATASOURCE_ADMIN_ERROREXTERNAL_DATASOURCE_ERROR(status / envelope /message不变)GET /external/tables、POST /external/tables/:remote/draft,服务抛出500 { error: 'No response from handler' }400 EXTERNAL_DATASOURCE_ERROR消费方核查:按 ADR-0112 的 sweep 清单(comparison / assignment / 类型联合 / 断言 / 文档各形态)grep 了 objectstack 与 objectui,没有任何分支读旧码;objectui 无需改动。
验证
pnpm --filter @objectstack/spec test— 276 文件 / 7149 测试通过pnpm --filter @objectstack/service-datasource test— 10 / 178 通过pnpm --filter @objectstack/rest test— 35 / 521 通过check:docs/check:api-surface/check:authorable-surface/check:route-envelope全绿;content/docs/references/由gen:schema && gen:docs再生check:generated聚合器目前在 main 上就因 feat(spec): 让 #4001 严格性账本接受机器校验(首次运行抓到 11 处漂移) #4232 未归类的check:strictness-ledger而失败(与本 PR 无关),已另行提 issue;上面因此逐个跑了对应 gate顺带发现(已按 Prime Directive #10 另行提 issue,不在本 PR 扩散范围)
GET /datasources、POST /external/refresh-catalog、POST /external/validate三条路由的服务抛出仍是适配器非信封 500 —— 与本 issue 点名的两条不同,它们不与任何契约分叉,属于纯未覆盖。check:generated在 main 上的失败(见上)。Generated by Claude Code