Skip to content

fix(service-datasource,rest,spec)!: external-datasource refusals answer their own error code (#4249) - #4263

Merged
os-zhuang merged 3 commits into
mainfrom
claude/datasource-admin-error-code-y1eels
Jul 31, 2026
Merged

fix(service-datasource,rest,spec)!: external-datasource refusals answer their own error code (#4249)#4263
os-zhuang merged 3 commits into
mainfrom
claude/datasource-admin-error-code-y1eels

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #4249

采用的方案:issue 选项 1 —— 注册 EXTERNAL_DATASOURCE_ERROR

三个候选里选了注册新码,理由与另外两个的排除法一致:

归属包的决定(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 对 503 message 的处理同构:归因来自路由实际分派的服务。三条 external 路由(GET /:name/remote-tablesPOST /:name/testPOST /:name/object-draft)的 400 error.code 由此变为 EXTERNAL_DATASOURCE_ERROR;六条 admin 路由字节不变。
  • rest/external-datasource-routes.ts:issue 指出的"同两个服务操作、两条路径、两种失败契约"就此收敛 —— GET /external/tablesPOST /external/tables/:remote/draft 此前完全没有 catch(服务一抛就是上面那个非信封 500),现在与 admin 路径一致地回 400 EXTERNAL_DATASOURCE_ERROR。import 路由的 EXTERNAL_IMPORT_ERROR 保持不变。
  • 测试:两个信封一致性套件的相应行随修复移动(含 issue 引用的那一行),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),含线上可见变化的完整清单。

线上可见变化

路由 之前 现在
三条 admin 表面的 external 路由,400 error.code DATASOURCE_ADMIN_ERROR EXTERNAL_DATASOURCE_ERROR(status / envelope / message 不变)
GET /external/tablesPOST /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 /datasourcesPOST /external/refresh-catalogPOST /external/validate 三条路由的服务抛出仍是适配器非信封 500 —— 与本 issue 点名的两条不同,它们不与任何契约分叉,属于纯未覆盖。
  • check:generated 在 main 上的失败(见上)。

Generated by Claude Code

…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
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 31, 2026 2:45am

Request Review

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/rest, @objectstack/service-datasource, @objectstack/spec.

108 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/connect-mcp.mdx (via @objectstack/rest)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/rest)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 31, 2026 02:13
@os-zhuang os-zhuang closed this Jul 31, 2026
@os-zhuang os-zhuang reopened this Jul 31, 2026
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 os-zhuang closed this Jul 31, 2026
@os-zhuang os-zhuang reopened this Jul 31, 2026
@os-zhuang
os-zhuang merged commit bcf1112 into main Jul 31, 2026
24 of 33 checks passed
@os-zhuang
os-zhuang deleted the claude/datasource-admin-error-code-y1eels branch July 31, 2026 03:00
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>
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The same three datasource-admin routes answer 400 with the wrong service's error code (#4225 follow-up, one field over)

2 participants