Repository navigation
Description
Found while working #10712 (PR #11059), which gave generateObjectDraft a
${namespace}_ prefix derived from the datasource's owning package. Filed rather than
fixed there: #10712 is scoped to the draft output, and this is a different path with
a different question behind it. Unassigned, awaiting first-touch grading.
What
ExternalDatasourceService.importObject reuses the draft pipeline, so it inherits the
prefix — but only when the caller does not name the object itself:
const name = opts.name ?? draft.name;opts.name is taken verbatim. A caller passing { "name": "customers" } gets an object
persisted as customers, with no ${namespace}_ prefix, via
persistObject → metadata.register('object', name, definition).
Reachable today
Not hypothetical, and not dependent on #10712's change. ImportObjectOpts extends GenerateDraftOpts with name?: string ("Override the auto-derived object name"), and
the import route forwards the request body as opts unfiltered —
packages/rest/src/external-datasource-routes.ts:462:
const result = await svc.importObject(
req.params.name,
/* remote */ …,
(req.body as Record<string, unknown>) ?? {},
);So POST /api/v1/datasources/:name/external/tables/:remote/import with
{"name": "customers"} reaches it.
Why this is not simply "the caller owns the name"
That reading is defensible on its own, and may well be the right ruling — but the
runtime write path this lands on does not apply the check that would catch it.
validateObjectNamespacePrefix runs in two places: defineStack() (compile time) and
MetadataManager.publishPackage / the protocol's publish pre-flight (the Studio
"publish" path, NAMESPACE_PREFIX). metadata.register('object', …) is neither. So an
imported object with an explicitly unprefixed name is registered and served without any
gate having looked at the name.
Note the in-tree callers do not currently exercise this: objectui's
ImportObjectDialog posts no name, and os datasource introspect does not import at
all. The exposure is the route body.
The question a first-touch should answer
Three shapes, none of which a dev should pick unilaterally:
- A. Apply the prefix to
opts.nametoo (samevalidateObjectNamespacePrefix
treatment as the derived name). Consistent, but silently rewrites a name the caller
explicitly asked for. - B. Refuse an
opts.namethat fails the prefix rule, with the rule's own actionable
message. Contract-first, and matches what the publish gate does with the same
violation — but it turns a currently-accepted request into a 4xx. - C. Leave it: an explicit override is the caller's decision. Then the gap worth
closing is arguably the broader one — thatmetadata.register('object', …)applies no
namespace check at all — which is a much wider question than this seam.
Related: #10712 / PR #11059 (the draft half, where the prefix derivation lives),
#11000 (the unauthorable primaryKey key in the same generator).
Activity
os-zhuang commentedon Aug 23, 2026
Triage:
pm:queue,domain:services, Bug.importObject'sopts.nameoverride bypasses the namespace prefix and persists an unprefixed federated object — invariant restore (the prefix rule is the declared behavior); loud refusal or forced prefixing, implementer argues which in the PR.Generated by Claude Code
claude commentedon Aug 23, 2026
Contract review — PASS(fable 席审核,session
5213b871-5164-5bc3-8874-28b336bbcd40)授权来源:maintainer 2026-08-23「要不还是你挂个定时处理审核吧」——常设合同审核职责。
降档保险丝读数(本席自读
get_session→external_metadata.last_served_model):claude-fable-5,符合CONTRACT_REVIEW_TIER(scripts/pm/dispatch-gates.mjs:2816)。审核对象:PR #11241(external-datasource import 命名前缀拒绝)。
判定依据:
importNameRefusedError走 The direct-mount REST package door answers 500 INTERNAL_ERROR for coded 4xx errors the dispatcher twin maps correctly #8016 拒绝信封:code: 'EXTERNAL_IMPORT_ERROR'、status: 400,refusal 在 draft 之前抛出,无部分持久化。opts.name !== undefined时触发,normaliseNamespace(await this.config.getNamespace?.(datasource))兼容无 namespace 配置的数据源(此时不拒绝,与现约定一致)。status).toBe(400)、code).toBe('EXTERNAL_IMPORT_ERROR')、message 含修复指引("Rename it to 'wh_orders'"),并断言 nothing persisted。结论:拒绝面收窄且 fail-closed,信封形状与 #8016 一致,无既有导入路径行为变化。PASS,清除
needs:contract-review(卡 + PR 双载体)。Generated by Claude Code