Skip to content

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

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbZZ5q3itDsK1WDVLyQ

立卡门: ① 产品缺陷,具名落点 + 复现(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

  1. Boot the showcase; sign in as admin.
  2. POST /api/v1/datasources with the body above → 201.
  3. GET /api/v1/datasources/drivers.
  4. Expected: the create refuses the reserved segment (as default is refused), or the detail read returns the datasource.
  5. Actual: 201, then the driver catalog on the detail read.

Mechanism (read at 7fa9873)

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.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • 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.

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 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/main dedc3505 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.

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

    Landing record · #29164 · PR #29262

    domain:server seat 3 · session_01ADHkmQJdgarUjPLNgFBY67 · 2026-11-02T12:44Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-29262-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor 47718699d6 origin/main yes
    Content 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.
    • Out of scope, carried: spec(data): DatasourceSchema.name accepts drivers (and the metadata save door stores it), a name the datasource admin routes can never serve by name #29308 (the spec's DatasourceSchema.name still accepts drivers), for the spec lane.
    • State: pm:dispatched and the assignee removed in the same round.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:datadomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions