Repository navigation
fix(service-datasource)!: refuse creating a datasource named drivers, the driver-catalog route literal - #29262
Conversation
…rst) Adds the reserved-name list derived against the admin registrar's literal siblings of /:name, a drift pin over the registrar's mounts, and door cases over the real Hono server: creating `drivers` must answer 400. Claude-Session: https://claude.ai/code/session_01ADHkmPjMhxWBYAk48z4Mam Co-authored-by: Claude <noreply@anthropic.com>
…route literal `GET /datasources/drivers` (the driver catalog) is mounted before `GET /datasources/:name`, so a runtime datasource named `drivers` was created but its detail read never reached. assertValidName now refuses every literal the admin registrar mounts beside a same-method `/:name` route, from the one list the registrar mounts the literal from; `default` is unchanged and `test` stays creatable (no `POST /:name` sibling). Claude-Session: https://claude.ai/code/session_01ADHkmPjMhxWBYAk48z4Mam Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 3 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 895984498754f55ac4a103eb39ffeccae49ea3ee && git checkout 895984498754f55ac4a103eb39ffeccae49ea3ee
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f2bf29386f7ee10bbe41f78cd1f0d7eab85f6c92 1384def35cb35d9efaa9a36a25e695b58bfbe34f && git checkout -B drift-repro f2bf29386f7ee10bbe41f78cd1f0d7eab85f6c92 && git merge --no-ff 1384def35cb35d9efaa9a36a25e695b58bfbe34f
node scripts/docs-audit/affected-docs.mjs --json f2bf29386f7ee10bbe41f78cd1f0d7eab85f6c92
|
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Contract-review-claim: session_01ADHkmQJdgarUjPLNgFBY67 · head a5f6f0c · 2026-11-02T11:31Z |
…mment on its own line Review fixes: the drivers page now says the datasource admin API refuses a runtime datasource named `drivers` (the driver-catalog route), and the route-shadowed-names comment in assertValidName moves off the closing-brace line. No behaviour change. Claude-Session: https://claude.ai/code/session_01ADHkmPjMhxWBYAk48z4Mam Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Contract-review-claim: session_01ADHkmQJdgarUjPLNgFBY67 · head 1384def · 2026-11-02T11:59Z |
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Contract reviewServed-tier: The delta from the previously reviewed head ① Derived judgments
② Semver levelThe changeset gives ③ Boundary flags
④ Design judgmentThe narrowing takes no declared capability away. Neither the spec nor
Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 席位核对 · ACCEPT · PR #29262 · 2026-11-02T12:00ZHead-sha: 维护者速读(席位终稿)
核对六项
|
Requested by Steve · project thread
Fixes #29164
Clause-②: no (narrowing)
Metadata-protocol: none
维护者速读
drivers(与default同样的 400DATASOURCE_ADMIN_ERROR,报错点名该名字);保留名单与路由注册用同一个常量,并有一条从注册器实际挂载推导的漂移测试。GET /datasources/drivers是驱动目录,先于GET /datasources/:name挂载,所以叫drivers的数据源能建、能列,但编辑表单永远打不开。drivers创建现在 400(收窄);test仍可创建;已存的drivers行不受影响。回滚即 revert。datasource-route-reserved-names.ts(名单与理由),datasource-admin-service.ts的assertValidName,__tests__/datasource-route-reserved-names.test.ts的漂移测试。pnpm --filter @objectstack/service-datasource exec vitest run src/__tests__/datasource-route-reserved-names.test.ts,预期Tests 7 passed (7)(main 上drivers用例红:expected 201 to be 400)。What changes
src/datasource-route-reserved-names.tsholdsDRIVER_CATALOG_SEGMENTandROUTE_SHADOWED_DATASOURCE_NAMES(today['drivers']): every literal segment the admin registrar mounts beside a same-method/datasources/:nameroute. Internal module, not exported from the package entry.admin-routes.tsmounts the driver catalog from that constant (${root}/${DRIVER_CATALOG_SEGMENT}); the wire path is unchanged.assertValidName(datasource-admin-service.ts) refuses each listed name after the existingdefaultcheck, with the same thrownError, so the route answers the same 400DATASOURCE_ADMIN_ERROR. The message names the reserved name. No new error code, status or member..changeset/29164-reserve-route-shadowed-datasource-names.md:patchon@objectstack/service-datasource, BREAKING banner, ADR-0087not-required (no-migration-prescription), both declaration lines.Which literals shadow a name (measured on the registrar). Single-segment literals under
/datasourcesareGET /driversandPOST /test./:nameroutes exist for GET, PATCH and DELETE only. SodriversshadowsGET /:name;testshadows nothing (there is noPOST /:name; the saved-datasource probe isPOST /:name/test, a different depth).testis therefore not reserved, and a test pins that a datasource namedtestis created (201) and read back by name (200).Tests
New file
src/__tests__/datasource-route-reserved-names.test.ts(7 cases). No existing test was edited.registerDatasourceAdminRoutesagainst a capturing mockIHttpServer(the route-ledger conformance seam), derives the literal single segments that share a method with a/:nameroute, and requiresROUTE_SHADOWED_DATASOURCE_NAMESto equal that set. A control asserts the enumeration sawGET /:nameandPOST /test. A second case pins the derivation itself:POST /testbesideGET /:namederives nothing,GET /testbeside it derivestest.HonoHttpServerand a realDatasourceAdminService:drivers→ 400DATASOURCE_ADMIN_ERROR, message names'drivers', nothing written;default→ same refusal, unchanged;reporting→ 201 and read back by name;test→ 201 and read back by name.driversrow seeded in the store stays listed, patchable and removable.Red on base, green on head, three ablations
41bae08f):Tests 2 failed | 5 passed (7);driverscreate: "expected 201 to be 400". (The second red was the stored-row case asserting 200 on a DELETE that answers 204; the case now assertsok. Not a behaviour change.)f085c3c6):Tests 7 passed (7).scripts/ablation-replace.mjs(anchor must hit, blob hashes, restore checked against HEAD). The test imports the service and registrar from source (relative paths), so no build sits between mutation and run.if (false && ROUTE_SHADOWED_DATASOURCE_NAMES.includes(name)), anchor 1 → 0, blob4ec99504→4226d0f7. ResultTests 1 failed | 6 passed (7): thedriversrefusal case, "expected 201 to be 400". Restored blob4ec99504= HEAD.defaultguard becameif (false && name === 'default'), blob4ec99504→fd350350. ResultTests 1 failed | 6 passed (7): thedefaultcase, "expected 201 to be 400". Restored = HEAD.GET ${root}/healthinadmin-routes.ts, blobb77801e2→214d385c. ResultTests 1 failed | 6 passed (7): "expected [ 'drivers' ] to deeply equal [ 'drivers', 'health' ]". Restored blobb77801e2= HEAD,git diff HEADempty. A first attempt with an anchor the replacement repeated was refused by the tool (anchor count 1 → 1) and restored; it did not run.Acceptance notes
driversrows. The refusal is at create only, and an update cannot rename (it keepsexisting.name). A row created before this release stays listed, patchable, removable and probe-able by name; its detail read still answers the catalog. Fixing that read would mean the catalog route resolving a datasource too, a second meaning on one path, so it is left out of scope. The changeset gives the remedy: recreate under another name, rebind the objects, remove the old row.DatasourceSchema.namerule (/^[a-z_][a-z0-9_]*$/) also acceptsdrivers, so a datasource declared in code under that name hits the same shadowed detail read. That is the metadata accept surface, spec-lane work, so it is not touched here; it is reported to the seat.GET /driversis reserved. Reservingtesttoo would refuse a common name that today works through every door. The drift pin reserves it the day a same-method/:namesibling mounts.Verification
All readings at head
2c7aac31(fixf085c3c6plus a merge oforigin/mainat104cd1a5).origin/mainmoved 24 commits after that merge; none touchespackages/services/service-datasource, and PR #29192 (same file, different function) had not landed.Package suite, typecheck, lint
pnpm --filter @objectstack/service-datasource test:Test Files 70 passed (70),Tests 1159 passed (1159), VERDICT command-exit 0.pnpm --filter @objectstack/service-datasource typecheck: exit 0.pnpm lint(repo-wideeslint . --no-inline-config): exit 0.Derived gates (dispatch-gates.mjs --commands, 62 families)
--ranreconciliation: "62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero, every line carrying its exit code).check-plugin-teardown-shape --self-teston a shallow clone (passed, 48 cases, aftergit fetch --unshallow origin main), andcheck:dual-build-cjs-loadsbefore every package was built (passed afterpnpm build, 117/117 tasks: 115 require entry points across 70 packages load).Full dogfood battery (owed by the narrowing arm)
2c7aac31, closure built withpnpm --filter '@objectstack/dogfood^...' build, thenpnpm --filter @objectstack/dogfood testunder the verify lock: VERDICT command-exit 0,Test Files 388 passed | 3 skipped (391),Tests 3208 passed | 28 skipped (3236), no failure.Generated by Claude Code