You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
datasources: a runtime datasource named "drivers" is accepted, but GET /api/v1/datasources/drivers answers the driver catalog, so its detail read is unreachable #29164
立卡门: ① 产品缺陷,具名落点 + 复现(reach: HTTP,POST /api/v1/datasources + GET /api/v1/datasources/drivers,同一启动实测两次)
业务理由: 管理员给数据源起名 drivers(一个自然的名字)时创建成功,但之后数据源编辑表单的详情读永远拿到的是驱动目录,表单打不开或显示错的数据,管理员看不出原因;default 已被保留拒绝,同类的路由字面段却没有。
动手的读者: 拥有 packages/services/service-datasource 的车道:assertValidName 拒绝注册器在 /:name 旁挂载的每个字面段(至少 drivers),答 400 DATASOURCE_ADMIN_ERROR 并点名保留段,与 default 同形。
查重: datasource drivers name shadowed 12 命中(无相关)、reserved datasource name 86(最近邻 #23634#23768,宿主保留 default,已关)、datasources drivers captured name 52(最近邻 #27512,草稿探测文件,已关),含已关;无重复。
Batch-of: release-18.3.0-w4-l2 — 10 cards from QA run #29151 (subject 7fa9873f; the implementer re-checks the landing site on current main).
QA-source: #29151 · integration-system.datasource-admin-lifecycle · observed beside the clauses
What happens
As admin on a stock showcase, POST /api/v1/datasources {"name":"drivers","driver":"sqlite","config":{"filename":"/tmp/x/drivers.db"}} answers 201 and GET /api/v1/datasources lists drivers. But GET /api/v1/datasources/drivers answers 200 {data:{drivers:[…catalog…]}}, not {data:{datasource}}. PATCH / DELETE /datasources/drivers, POST /drivers/test, /drivers/migrate-credential and GET /api/v1/meta/datasource/drivers all reach the row; only the detail read (the credential-stripped view the edit form uses, with hasSecret and redactedConfigKeys) cannot be served.
Measured at framework 7fa9873f (main, 2026-11-02); reproduced twice, with and without a secret.
Reproduction
Boot the showcase; sign in as admin.
POST /api/v1/datasources with the body above → 201.
GET /api/v1/datasources/drivers.
Expected: the create refuses the reserved segment (as default is refused), or the detail read returns the datasource.
Actual: 201, then the driver catalog on the detail read.
assertValidName (packages/services/service-datasource/src/datasource-admin-service.ts:1350-1364) reserves only default. The registrar mounts the static GET ${root}/drivers (admin-routes.ts:695) before GET ${root}/:name (:749), whose comment says the order exists "so that literal segment is never captured as a name"; the create door still mints the name.
Read fresh. The fix lands in packages/services/service-datasource/src/datasource-admin-service.ts assertValidName, which reserves only default while admin-routes.ts mounts the literal /drivers segment before /:name. A datasource whose detail read the edit form cannot reach is a user-visible behaviour defect, so p2. Location and repro are named, so pm:queue.
Claim: PM loop round 1 · card thread · 2026-11-02T09:24Z
Session: session_01ADHkmQJdgarUjPLNgFBY67 (the seat); the card thread is session_01ADHkmPjMhxWBYAk48z4Mam
Account: os-steve (the seat's linked user; the card's assignee)
Branch: claude/project-thread-0rjenl
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#3
Ruling-ref: the maintainer, verbatim, in the project thread: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」
File surface: packages/services/service-datasource/src/datasource-admin-service.ts (assertValidName reserved route segments) + a pin derived from admin-routes.ts + tests + one changeset
Container & model: card thread cmsg_01FdL1XeUyvVNxLreh3F4Qi94YCYBg2S8baVHuFe3JkctW, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
Clause-②: no (narrowing)
Metadata-protocol: none
Responsibility: the card thread implements and opens the draft PR; the seat reviews (contract review at CONTRACT_REVIEW_TIER + full dogfood owed), records and lands
Thread-read: 6573290771
Premise: holds (site re-read on origin/maindedc3505 by the seat before dispatch)
Serial constraints cleared: serial with PR #29192 (same file datasource-admin-service.ts; this card starts after it lands or rebases on it)
Resume-from: none — fresh from origin/main
State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim. Order: project file server-seat3/standard-order.md + server-seat3/29164-delta.md.
{
"issue": 29164,
"status": "done",
"branch": "claude/project-thread-0rjenl",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29262",
"session": "session_01ADHkmPjMhxWBYAk48z4Mam",
"head": "a5f6f0cf78",
"premise_still_valid": true,
"summary": "assertValidName now refuses every literal the admin registrar mounts beside a same-method /datasources/:name route, today only `drivers` (GET /drivers is mounted before GET /:name and answers the driver catalog). Same 400 DATASOURCE_ADMIN_ERROR as `default`, message names the name; no new code or member. The list lives in a new internal module; the registrar mounts the catalog from the same constant and a drift pin derives the shadowing set from the registrar's real mounts. `test` is NOT reserved: POST /test has no POST /:name sibling, and a datasource named `test` is pinned 201 + readable by name. A stored `drivers` row stays listed/patchable/removable (update cannot rename); its detail read still answers the catalog, left out of scope.",
"tests": "New src/__tests__/datasource-route-reserved-names.test.ts, 7 cases, no existing test edited. Red first at 41bae08f: 'expected 201 to be 400' on the drivers create. Green at f085c3c6: 7/7. Ablations via scripts/ablation-replace.mjs from source imports (no build in the path): (1) new guard disabled, blob 4ec99504->4226d0f7, 1 failed (drivers, expected 201 to be 400); (2) default guard disabled, 4ec99504->fd350350, 1 failed (default case); (3) unreserved GET ${root}/health mounted, b77801e2->214d385c, 1 failed (expected [drivers] to deeply equal [drivers, health]); each restore blob == HEAD. A first ablation-3 attempt was refused by the tool (anchor repeated in replacement, 1->1) and did not run. Final head a5f6f0cf: pnpm --filter @objectstack/service-datasource test 70/70 files, 1160/1160 tests; typecheck exit 0.",
"gates": "Final head a5f6f0cf: dispatch-gates --commands derived 62 families, all 62 run, each exit 0; --ran: '62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN' (derived zero). pnpm lint (repo-wide) exit 0. Earlier head 2c7aac31 same 62 green after two prerequisite exit-3s (teardown-shape self-test on shallow clone, dual-build-cjs-loads before full build) were cleared by --unshallow and pnpm build. PR check-runs at a5f6f0cf: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (8/8 shards), Build Core success, Governed Surface Queue Guard success, Test UI success, Live E2E UI success, Dogfood Regression Gate success, Check Changeset success, PR-body guards success; Lint UI / Type Check UI / Build & E2E UI skipped by path filter.",
"dogfood_battery": "a5f6f0cf · os-verify-lock VERDICT command-exit 0 · Test Files 389 passed | 3 skipped (392) · Tests 3215 passed | 28 skipped (3243) · no failure. (Also run on 2c7aac31 before #29192 landed: 388/391 files, 3208 passed, 0 failed.)",
"line_budget": "5 files, +276 / -1 vs main: test 207, new module 38, changeset 18, service 11, registrar 3/-1.",
"files_changed": [
".changeset/29164-reserve-route-shadowed-datasource-names.md",
"packages/services/service-datasource/src/datasource-route-reserved-names.ts",
"packages/services/service-datasource/src/datasource-admin-service.ts",
"packages/services/service-datasource/src/admin-routes.ts",
"packages/services/service-datasource/src/__tests__/datasource-route-reserved-names.test.ts"
],
"deviations": [
"PR #29192 landed at 10:35Z, two minutes before the PR opened (10:37Z) from head 2c7aac31; main was merged again right after (a5f6f0cf, clean, delta still the 5 files) and package suite, typecheck, all 62 gates, pnpm lint and the full dogfood were re-run there. The PR body's Verification section quotes 2c7aac31 (body is written once); the a5f6f0cf readings are in this report.",
"PR assignee not set: os-dev forbids assignees at pr_create and the standard order forbids MCP writes, so the harness's assign-and-request-review step was skipped."
],
"mcp_calls": "6 — reads only: issue_read get_comments x3, pull_request_read get x2, pull_request_read get_check_runs x1",
"api_writes": "2 — fleet relay pr_create (PR #29262, 8070 bytes sent = stored); this os-dev-report comment via post-stamped.mjs",
"open_questions": [
{
"question": "Reserve `test` too? The ruling says reserve every literal mounted beside /:name; the PM assumption asks to reserve those that actually shadow. Measured: POST /test shares no method with a /:name route, so a datasource named `test` works through every door today.",
"options": [
"A: reserve only same-method shadowing literals (drivers) — the drift pin adds `test` the day a GET/PATCH/DELETE /test mounts",
"B: reserve every single-segment literal regardless of method (drivers, test) — refuses a common working name"
],
"recommendation": "A, because B narrows a name that has no defect, and the method-aware drift pin closes the future case."
}
],
"out_of_scope_findings": [
"class: a · reach: inferred, same mechanism as the card's measured repro (GET /api/v1/datasources/drivers answers the catalog); not measured for a code origin · evidence: spec DatasourceSchema.name (/^[a-z_][a-z0-9_]*$/, packages/spec/src/data/datasource.zod.ts) accepts `drivers` for a code-declared datasource, whose detail read is shadowed the same way; that is the metadata accept surface, so spec-lane · dedupe words: datasource name drivers reserved, code datasource detail shadowed, DatasourceSchema name rule · carrier: spec seat"
]
}
git grep -n "The driver-catalog literal" origin/main -- packages/services/service-datasource/src/datasource-route-reserved-names.ts
line 30
Landed: merged as 47718699. Creating a runtime datasource named drivers through the admin API is now refused with 400 DATASOURCE_ADMIN_ERROR, because GET /api/v1/datasources/drivers is the driver catalog. The reserved names are derived from the registrar's real mounts, and a drift pin guards them. default is unchanged and test stays allowed.
Seat ACCEPT 6576611209 and the CONTRACT_REVIEW_TIER record on head 1384def3 are on the PR (Clause-②: no (narrowing), full dogfood VERDICT 0). Fixes #29164 closed this card on merge.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: integration-system.datasource-admin-lifecycle
Triage:
domain:server· p2 ·pm:queue· 2026-11-02T09:09Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Claim: PM loop round 1 · card thread · 2026-11-02T09:24Z
Session:
session_01ADHkmQJdgarUjPLNgFBY67(the seat); the card thread issession_01ADHkmPjMhxWBYAk48z4MamAccount:
os-steve(the seat's linked user; the card's assignee)Branch:
claude/project-thread-0rjenlWorktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#3Ruling-ref: the maintainer, verbatim, in the project thread: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」
File surface:
packages/services/service-datasource/src/datasource-admin-service.ts(assertValidNamereserved route segments) + a pin derived fromadmin-routes.ts+ tests + one changesetContainer & model: card thread
cmsg_01FdL1XeUyvVNxLreh3F4Qi94YCYBg2S8baVHuFe3JkctW,mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: highClause-②: no (narrowing)
Metadata-protocol: none
Responsibility: the card thread implements and opens the draft PR; the seat reviews (contract review at CONTRACT_REVIEW_TIER + full dogfood owed), records and lands
Thread-read: 6573290771
Premise: holds (site re-read on
origin/maindedc3505by the seat before dispatch)Serial constraints cleared: serial with PR #29192 (same file
datasource-admin-service.ts; this card starts after it lands or rebases on it)Resume-from: none — fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-steve, recorded by this claim. Order: project file server-seat3/standard-order.md + server-seat3/29164-delta.md.objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmPjMhxWBYAk48z4Mam
os-dev-report
{ "issue": 29164, "status": "done", "branch": "claude/project-thread-0rjenl", "pr": "https://github.com/objectstack-ai/objectstack/pull/29262", "session": "session_01ADHkmPjMhxWBYAk48z4Mam", "head": "a5f6f0cf78", "premise_still_valid": true, "summary": "assertValidName now refuses every literal the admin registrar mounts beside a same-method /datasources/:name route, today only `drivers` (GET /drivers is mounted before GET /:name and answers the driver catalog). Same 400 DATASOURCE_ADMIN_ERROR as `default`, message names the name; no new code or member. The list lives in a new internal module; the registrar mounts the catalog from the same constant and a drift pin derives the shadowing set from the registrar's real mounts. `test` is NOT reserved: POST /test has no POST /:name sibling, and a datasource named `test` is pinned 201 + readable by name. A stored `drivers` row stays listed/patchable/removable (update cannot rename); its detail read still answers the catalog, left out of scope.", "tests": "New src/__tests__/datasource-route-reserved-names.test.ts, 7 cases, no existing test edited. Red first at 41bae08f: 'expected 201 to be 400' on the drivers create. Green at f085c3c6: 7/7. Ablations via scripts/ablation-replace.mjs from source imports (no build in the path): (1) new guard disabled, blob 4ec99504->4226d0f7, 1 failed (drivers, expected 201 to be 400); (2) default guard disabled, 4ec99504->fd350350, 1 failed (default case); (3) unreserved GET ${root}/health mounted, b77801e2->214d385c, 1 failed (expected [drivers] to deeply equal [drivers, health]); each restore blob == HEAD. A first ablation-3 attempt was refused by the tool (anchor repeated in replacement, 1->1) and did not run. Final head a5f6f0cf: pnpm --filter @objectstack/service-datasource test 70/70 files, 1160/1160 tests; typecheck exit 0.", "gates": "Final head a5f6f0cf: dispatch-gates --commands derived 62 families, all 62 run, each exit 0; --ran: '62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN' (derived zero). pnpm lint (repo-wide) exit 0. Earlier head 2c7aac31 same 62 green after two prerequisite exit-3s (teardown-shape self-test on shallow clone, dual-build-cjs-loads before full build) were cleared by --unshallow and pnpm build. PR check-runs at a5f6f0cf: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (8/8 shards), Build Core success, Governed Surface Queue Guard success, Test UI success, Live E2E UI success, Dogfood Regression Gate success, Check Changeset success, PR-body guards success; Lint UI / Type Check UI / Build & E2E UI skipped by path filter.", "dogfood_battery": "a5f6f0cf · os-verify-lock VERDICT command-exit 0 · Test Files 389 passed | 3 skipped (392) · Tests 3215 passed | 28 skipped (3243) · no failure. (Also run on 2c7aac31 before #29192 landed: 388/391 files, 3208 passed, 0 failed.)", "line_budget": "5 files, +276 / -1 vs main: test 207, new module 38, changeset 18, service 11, registrar 3/-1.", "files_changed": [ ".changeset/29164-reserve-route-shadowed-datasource-names.md", "packages/services/service-datasource/src/datasource-route-reserved-names.ts", "packages/services/service-datasource/src/datasource-admin-service.ts", "packages/services/service-datasource/src/admin-routes.ts", "packages/services/service-datasource/src/__tests__/datasource-route-reserved-names.test.ts" ], "deviations": [ "PR #29192 landed at 10:35Z, two minutes before the PR opened (10:37Z) from head 2c7aac31; main was merged again right after (a5f6f0cf, clean, delta still the 5 files) and package suite, typecheck, all 62 gates, pnpm lint and the full dogfood were re-run there. The PR body's Verification section quotes 2c7aac31 (body is written once); the a5f6f0cf readings are in this report.", "PR assignee not set: os-dev forbids assignees at pr_create and the standard order forbids MCP writes, so the harness's assign-and-request-review step was skipped." ], "mcp_calls": "6 — reads only: issue_read get_comments x3, pull_request_read get x2, pull_request_read get_check_runs x1", "api_writes": "2 — fleet relay pr_create (PR #29262, 8070 bytes sent = stored); this os-dev-report comment via post-stamped.mjs", "open_questions": [ { "question": "Reserve `test` too? The ruling says reserve every literal mounted beside /:name; the PM assumption asks to reserve those that actually shadow. Measured: POST /test shares no method with a /:name route, so a datasource named `test` works through every door today.", "options": [ "A: reserve only same-method shadowing literals (drivers) — the drift pin adds `test` the day a GET/PATCH/DELETE /test mounts", "B: reserve every single-segment literal regardless of method (drivers, test) — refuses a common working name" ], "recommendation": "A, because B narrows a name that has no defect, and the method-aware drift pin closes the future case." } ], "out_of_scope_findings": [ "class: a · reach: inferred, same mechanism as the card's measured repro (GET /api/v1/datasources/drivers answers the catalog); not measured for a code origin · evidence: spec DatasourceSchema.name (/^[a-z_][a-z0-9_]*$/, packages/spec/src/data/datasource.zod.ts) accepts `drivers` for a code-declared datasource, whose detail read is shadowed the same way; that is the metadata accept surface, so spec-lane · dedupe words: datasource name drivers reserved, code datasource detail shadowed, DatasourceSchema name rule · carrier: spec seat" ] }Stamped 2026-11-02T11:30Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Landing record · #29164 · PR #29262
domain:serverseat 3 ·session_01ADHkmQJdgarUjPLNgFBY67· 2026-11-02T12:44Zgit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-29262-*'git merge-base --is-ancestor 47718699d6 origin/maingit grep -n "The driver-catalog literal" origin/main -- packages/services/service-datasource/src/datasource-route-reserved-names.ts47718699. Creating a runtime datasource nameddriversthrough the admin API is now refused with 400DATASOURCE_ADMIN_ERROR, becauseGET /api/v1/datasources/driversis the driver catalog. The reserved names are derived from the registrar's real mounts, and a drift pin guards them.defaultis unchanged andteststays allowed.6576611209and theCONTRACT_REVIEW_TIERrecord on head1384def3are on the PR (Clause-②: no (narrowing), full dogfood VERDICT 0).Fixes #29164closed this card on merge.DatasourceSchema.nameacceptsdrivers(and the metadata save door stores it), a name the datasource admin routes can never serve by name #29308 (the spec'sDatasourceSchema.namestill acceptsdrivers), for the spec lane.pm:dispatchedand the assignee removed in the same round.