Skip to content

fix(spec)!: a datasource named drivers is refused at every door that parses a datasource - #29423

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-rx0qzr
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-rx0qzr

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
Fixes #29308

Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none in skills/** (skills/objectstack-data/rules/datasources.md teaches default as host-reserved, and no file under skills/** declares or shows a datasource named drivers; every taught use stays true). Docs: content/docs/data-modeling/drivers.mdx said drivers was reserved at the admin API only; it now names the schema refusal and every door.

维护者速读

  • 改了什么:数据源名 drivers 现在在所有解析数据源的入口被拒收(代码声明、os validate、元数据保存门 422),不再只在管理接口的新建门拒收;保留名单搬进 spec,成为唯一一份,管理接口的路由和新建门都读它。
  • 为什么改:GET /api/v1/datasources/drivers 是驱动目录,叫 drivers 的数据源永远打不开编辑页;新建门拦住了,但代码和元数据保存门还能造出这个打不开的数据源。
  • 风险与回滚:收窄,叫 drivers 的数据源再保存会被拒;仓内作者写的此名数据源为 0。已存的行照常加载,_diagnostics 标 valid: false,管理接口里仍可列出、删除、只改标签。回滚即还原本 PR。
  • 先看:packages/spec/src/data/datasource.zod.ts(新常量与 name 上的第二条 pattern)、packages/services/service-datasource/src/datasource-route-reserved-names.ts(改读 spec 常量)、迁移条目 18.datasource-name-route-shadowed-refused.ts。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/data/datasource-route-shadowed-name.test.ts,预期 7 项全绿,其中一项断言报错落在 name、以 Datasource name 'drivers' is reserved 开头。
  • 需要你做的:新公开导出名 ROUTE_SHADOWED_DATASOURCE_NAMES 需要你点头(席位会问);其余无,席位落地(落地前走一次契约复核)。

What changes

  • One list, in the spec. New export ROUTE_SHADOWED_DATASOURCE_NAMES in packages/spec/src/data/datasource.zod.ts (@objectstack/spec/data), a frozen ['drivers'] as const: the names the datasource admin API can never serve by name, because a literal route under /api/v1/datasources/ shares a method with the by-name read and is mounted ahead of it (GET /api/v1/datasources/drivers is the driver catalog).
  • DatasourceSchema.name refuses each entry. A second .regex() on name (a negative lookahead built from the list), with the message Datasource name 'drivers' is reserved: the datasource admin API serves its own route at /api/v1/datasources/drivers, ahead of the by-name read /api/v1/datasources/:name, so a datasource of that name could never be read by name. Pick another name. One invalid_format issue at name. A pattern, not a refinement: zod emits both patterns into the published JSON Schema (allOf), so no dropped-refinements.baseline.json row is added (the build reads 988 sites before and after) and JSON-Schema consumers refuse the name too (pinned).
  • The admin routes read the spec list (the one declared cross-lane file, packages/services/service-datasource/src/datasource-route-reserved-names.ts). ROUTE_SHADOWED_DATASOURCE_NAMES there is now the spec's frozen array itself (widened to readonly string[] for the create door's includes), and DRIVER_CATALOG_SEGMENT, the segment the registrar mounts the catalog from, is typed (typeof ROUTE_SHADOWED_DATASOURCE_NAMES)[number] against the spec list, so a catalog segment the spec does not reserve does not compile. The registrar (admin-routes.ts) and the create door (datasource-admin-service.ts) are unchanged: they already imported from this file.
  • Create door: no double refusal. createDatasource runs assertValidName (which refuses drivers with its own 400 DATASOURCE_ADMIN_ERROR sentence) before any schema parse, so a POST /api/v1/datasources for drivers still answers that one refusal; the existing door pins hold unchanged (below).
  • Semantic migration entry datasource-name-route-shadowed-refused (step 18), modelled on stack-datasource-host-reserved-name-refused: refused with a prescription, no conversion (the new name, and every object that routes to it, are the author's call).
  • Docs. content/docs/data-modeling/drivers.mdx: the sentence PR fix(service-datasource)!: refuse creating a datasource named drivers, the driver-catalog route literal #29262 added ("reserved at the datasource admin API") now names the schema refusal and every door. Regenerated: content/docs/references/data/datasource.mdx, packages/spec/api-surface/data.json, packages/spec/export-origins/data.json.
  • Not changed. default (the host's own default row still parses through DatasourceSchema; a stack still may not declare it, stack.zod.ts), test (the draft-probe literal shadows no by-name read), and names that only contain the word (driver, drivers_archive, my_drivers, _drivers), all pinned as controls.

Tests

  • New packages/spec/src/data/datasource-route-shadowed-name.test.ts (7 cases): the list is frozen and every entry matches the identifier pattern (so the refusal pattern needs no escaping); DatasourceSchema refuses each entry with one invalid_format issue at name whose message names the name and the route and carries no tracker number; defineStack refuses it with STACK_SCHEMA_INVALID / 422 at datasources.0.name; the published JSON Schema's name patterns reject each entry and accept reporting, drivers_archive, driver; controls parse.
  • Forced test edits: none. packages/services/service-datasource/src/__tests__/datasource-route-reserved-names.test.ts (the drift pin and the door cases) is unchanged and green.

Acceptance notes

  • Save door, measured before and after (one-shot probe, a scratch saveMetaItem test over the stub-engine harness of protocol.code-defined-datasource-door.test.ts, both kernel shapes; not committed). Before (eef116e4): PUT datasource/drivers saved (success: true, row stored), and a stored drivers row read _diagnostics.valid: true. After (4df813f3): INVALID_METADATA / 422 with datasource/drivers failed spec validation: name: Datasource name 'drivers' is reserved: …, nothing stored; the control drivers_ok saves; a stored drivers row still loads with _diagnostics: { valid: false, errors: [{ path: 'name', code: 'invalid_format', … }] }.
  • Admin door on a stored drivers row (written before PR fix(service-datasource)!: refuse creating a datasource named drivers, the driver-catalog route literal #29262): list, label/active-only PATCH and DELETE still work (the existing pin). A PATCH that touches config, driver, pool or external, or carries a secret, is judged against the whole DatasourceSchema (assertValidRecord) and is now refused with the schema's sentence: the row has to be renamed, which only a new datasource plus re-pointing the objects can do. Same posture as every other spec refusal on a stored row.
  • Blast radius: 0. git grep for a datasource named drivers across examples/, packages/** (templates, fixtures), ui/, apps/, content/: one hit, the service-datasource stored-row fixture above.
  • New public name for the maintainer: ROUTE_SHADOWED_DATASOURCE_NAMES (the name the service-datasource module already used). Open question in the report.
  • packages/lint does not validate datasource names (git grep DatasourceSchema packages/lint/src: 0 hits), so it owes no reading.

Verification

All readings at head 4df813f3 (the last commit), tree objectstack-ai/objectstack, each through os-verify-lock (VERDICT command-exit 0 on every line).

Suites (spec, the consumers, full dogfood)
  • Closure build: pnpm --filter '@objectstack/dogfood^...' --filter '@objectstack/service-datasource...' --filter '@objectstack/metadata-protocol...' build, exit 0 (386 s).
  • pnpm --filter @objectstack/spec test: Test Files 792 passed (792), Tests 23398 passed, 1 todo.
  • pnpm --filter @objectstack/spec typecheck: exit 0.
  • pnpm --filter @objectstack/service-datasource test: Test Files 70 passed (70), Tests 1160 passed (1160), the unchanged datasource-route-reserved-names.test.ts among them. Typecheck exit 0.
  • pnpm --filter @objectstack/metadata-protocol test: Test Files 303 passed, 4 skipped (307), Tests 22136 passed, 26 skipped.
  • Full dogfood (narrowing arm), pnpm --filter @objectstack/dogfood test, no shard, no path: Test Files 393 passed, 3 skipped (396), Tests 3243 passed, 28 skipped (3271), exit 0 (29 m).
  • spec build: dropped-refinements reads 988 sites, as on main (the refusal is a pattern, not a refinement).
Derived gates (107) and the changeset axes
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 107 commands for this change set; all 107 ran and exited 0. check:skill-examples and check:dual-build-cjs-loads first answered exit 3 (prerequisite: built dists) and were re-run after turbo run build --filter='./packages/**' (exit 0): 281 prose examples type-check; 115 require entry points across 70 packages load. --ran reconciliation: 107 derived, 107 run, 0 NOT-MEASURED, 0 UNRUN.
  • Offline PR-body check: check-changeset-no-major.mjs --base eef116e4 --event (this body's two declaration lines): exit 0, LEVEL AXIS accounted (@objectstack/spec: minor, @objectstack/service-datasource: patch).
  • check:adr-0087-registration: exit 0, registered datasource-name-route-shadowed-refused (new here).
  • Local-only check:dev-prereqs: exit 1, "the workspace is not built" for 35 ui/ packages this card never builds (local container state; not a derived family).

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01ADHkmS1mEJA9L4pTrajNhU

The reserved set moves to @objectstack/spec as ROUTE_SHADOWED_DATASOURCE_NAMES;
service-datasource's admin registrar and create door read the spec list.

Claude-Session: https://claude.ai/code/session_01ADHkmS1mEJA9L4pTrajNhU
Co-authored-by: Claude <noreply@anthropic.com>
…changeset

ADR-0087 semantic entry datasource-name-route-shadowed-refused, regenerated
api-surface / export-origins / datasource reference, and the drivers.mdx
sentence now names the schema refusal.

Claude-Session: https://claude.ai/code/session_01ADHkmS1mEJA9L4pTrajNhU
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-datasource, @objectstack/spec, touching 13 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/services/service-datasource/src/datasource-admin-service.ts, packages/spec/api-surface/data.json, packages/spec/export-origins/data.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via DatasourceSchema (symbol, a top-level const), ROUTE_SHADOWED_DATASOURCE_NAMES (symbol, a top-level const object), /api/v1/datasources (route, a path literal in routeShadowedNameRefusal), /api/v1/datasources/:name (route, a path literal in a comment on a changed line; a path literal in reason; a path literal in routeShadowedNameRefusal), /api/v1/datasources/drivers (route, a path literal in a comment on a changed line; a path literal in reason), /datasources/:name (route, a path literal in a comment on a changed line))
  • content/docs/data-modeling/external-datasources.mdx (via /api/v1/datasources (route, a path literal in routeShadowedNameRefusal))
  • content/docs/kernel/cluster.mdx (via /api/v1/datasources (route, a path literal in routeShadowedNameRefusal), /api/v1/datasources/:name (route, a path literal in a comment on a changed line; a path literal in reason; a path literal in routeShadowedNameRefusal), /datasources/:name (route, a path literal in a comment on a changed line))
  • content/docs/protocol/kernel/error-handling.mdx (via /api/v1/datasources (route, a path literal in routeShadowedNameRefusal), /api/v1/datasources/:name (route, a path literal in a comment on a changed line; a path literal in reason; a path literal in routeShadowedNameRefusal), /datasources/:name (route, a path literal in a comment on a changed line))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via DatasourceSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-1.mdx (via /api/v1/datasources (route, a path literal in routeShadowedNameRefusal), /api/v1/datasources/:name (route, a path literal in a comment on a changed line; a path literal in reason; a path literal in routeShadowedNameRefusal), /datasources/:name (route, a path literal in a comment on a changed line))
  • content/docs/releases/v17/17-6.mdx (via DatasourceSchema (symbol, a top-level const), /api/v1/datasources (route, a path literal in routeShadowedNameRefusal), /api/v1/datasources/:name (route, a path literal in a comment on a changed line; a path literal in reason; a path literal in routeShadowedNameRefusal), /datasources/:name (route, a path literal in a comment on a changed line))
  • content/docs/releases/v17/17-7.mdx (via /api/v1/datasources (route, a path literal in routeShadowedNameRefusal), /api/v1/datasources/:name (route, a path literal in a comment on a changed line; a path literal in reason; a path literal in routeShadowedNameRefusal), /datasources/:name (route, a path literal in a comment on a changed line))
  • content/docs/releases/v18/18-2.mdx (via /api/v1/datasources (route, a path literal in routeShadowedNameRefusal), /api/v1/datasources/:name (route, a path literal in a comment on a changed line; a path literal in reason; a path literal in routeShadowedNameRefusal), /datasources/:name (route, a path literal in a comment on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/services/service-datasource/src/datasource-admin-service.ts, packages/spec/api-surface/data.json, packages/spec/export-origins/data.json) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 96fb7b5d07afaf828f4c0de4ba0fe3d179984e86 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from ce6a2047d6ea13af52afd5a827beaa7a591f84e8 — the merge of head e5f6120b7f55592da59b6e9d2b7f710fe25be6a8 into base 96fb7b5d07afaf828f4c0de4ba0fe3d179984e86, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ce6a2047d6ea13af52afd5a827beaa7a591f84e8 && git checkout ce6a2047d6ea13af52afd5a827beaa7a591f84e8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96fb7b5d07afaf828f4c0de4ba0fe3d179984e86 e5f6120b7f55592da59b6e9d2b7f710fe25be6a8 && git checkout -B drift-repro 96fb7b5d07afaf828f4c0de4ba0fe3d179984e86 && git merge --no-ff e5f6120b7f55592da59b6e9d2b7f710fe25be6a8

node scripts/docs-audit/affected-docs.mjs --json 96fb7b5d07afaf828f4c0de4ba0fe3d179984e86

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 96fb7b5d07afaf828f4c0de4ba0fe3d179984e86 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Contract-review-claim: session_01ADHkmSduNGC2SZXszyrNut · head 4df813f · 2026-11-02T14:39Z

The lane seat reviews this head itself at CONTRACT_REVIEW_TIER (triage ruling 6575141496 on #28741 returned contract review to the lane): a narrowing of DatasourceSchema.name (a datasource named drivers is refused) and a new export ROUTE_SHADOWED_DATASOURCE_NAMES.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

待裁:新公开名字 ROUTE_SHADOWED_DATASOURCE_NAMES(#29308)

  • 一句话问题:名叫 drivers 的数据源能保存,但管理接口 GET /api/v1/datasources/drivers 被驱动目录路由抢走,这个数据源永远无法按名字读到。这个 PR 让 spec 拒收这类名字。被保留的名字放在一个新的公开导出常量里,路由也从它挂载,名字需要您定。
  • 背景:分诊 6576569136 定"保留集取自一个导出常量,路由也从它挂载";席位定由 spec 持有,service-datasource 引用它。
  • 选项:
    • A 保留 ROUTE_SHADOWED_DATASOURCE_NAMES(冻结数组 ['drivers']):零额外工作,名字直说原因:被路由遮住的数据源名。
    • B 改名(例如 RESERVED_DATASOURCE_NAMES):更短,但不说明为什么保留。改名要重新生成 api-surface/export-origins 并出复审 delta,约半小时。
  • 附带一问(开发 Q2):service-datasource 的 DRIVER_CATALOG_SEGMENT 按这个数组做类型约束(A,已实现),而不是 spec 再导出第二个常量(B)。推荐 A,少一个公开名字。
  • 推荐:两问都是 A。盲区:将来若有第二条遮名路由,数组会变长,名字仍然成立。
  • 自检:只看①选 A;②③④ 是否翻转:否。
  • 落地:在此卡标签取下前,席位不挂自动合并。

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 4df813f3ea788b1ac5904d651a2271bff8dde85f
Local-runs: none
Scope: packages/spec/src/data/datasource.zod.ts, packages/spec/src/migrations/entries/semantic/18.datasource-name-route-shadowed-refused.ts + 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, packages/spec/src/data/datasource-route-shadowed-name.test.ts, packages/spec/src/stack.zod.ts, packages/spec/src/kernel/metadata-type-schemas.ts, packages/metadata-protocol/src/protocol-save-door-refusals.ts, packages/spec/src/migrations/entries/semantic/18.stack-datasource-host-reserved-name-refused.ts, packages/spec/scripts/build-migration-registry.ts, .changeset/29308-datasource-route-shadowed-name.md

Inputs read: card #29308 (body, triage 6576569136, claim 6578377666, os-dev-report 6579884811), PR #29423 (body, 9-file list, net diff against merge-base eef116e4 of main), the check-runs on the head (two polls), the live tree at the head via git show. The remainder of the diff (the test file, the docs, the regenerated references, the changeset, the service-datasource re-export) is the lane seat's ACCEPT at TIER_DEFAULT; it is READ here to judge the two hits files, not reviewed.

Check-runs on the head at the second poll: 0 failures. Green: Check Changeset, Governed Surface Queue Guard, PR-body guards, Spec property liveness, Dogfood Regression Gate, Lint (CI/release/engine gates, ESLint, docs & code guards, repo & tooling gates, PM dispatch-gates, scoped x3), Type Check (source gates, debt ledger), Build Docs, Build UI Deps, Live E2E UI, Check Documentation Links, Flag docs, Check PR Size. Still in_progress: Build Core, Test Core (shards 3-8), Test UI (1-16), Type Check (workspace, consumer gates, UI). The landing rule (AGENTS.md #14, Tier S) needs every check green independently of this record; nothing read here predicts a red.

① Derived judgments

Accept-set changes the diff implies, each judged against the live tree:

  1. DatasourceSchema.name refuses drivers (one invalid_format issue at name), via a second .regex() with a negative lookahead built from ROUTE_SHADOWED_DATASOURCE_NAMES. RIGHT. The first pattern (^[a-z_][a-z0-9_]*$) still fires first for off-pattern input, so Drivers meets the identifier refusal and not this one (pinned as a control). Negative-lookahead patterns are established spec practice (flow.zod.ts:623, override-declaration.zod.ts:62, datasource.zod.ts:181), so no portability rule is broken.
  2. Every door that parses DatasourceSchema inherits the refusal: defineDatasource (datasource.zod.ts:1019, parses the schema), a stack's datasources (stack.zod.ts:695, z.array(DatasourceSchema)), and the metadata save door (metadata-type-schemas.ts:131 maps datasource to DatasourceSchema; protocol-save-door-refusals.ts:454/522 turn a failed parse into 422 INVALID_METADATA). RIGHT; the PR body's "every door" claim holds in the cited code.
  3. The published JSON Schema carries the refusal as a second pattern (zod 4 allOf), so JSON-Schema consumers refuse the name too. RIGHT, and pinned by the new test; no dropped-refinements.baseline.json row is owed because a .regex() check is not a refinement.
  4. default stays accepted by DatasourceSchema and stays refused only on the stack collection (stack.zod.ts:643-660); test stays accepted. RIGHT (both pinned as controls); the constant's docblock states why default is not in the list, matching the stack file's own rule.
  5. The admin create door (datasource-admin-service.ts:818-819) runs assertValidName before any schema parse and refuses drivers with its own 400 sentence; no double refusal. RIGHT; the door pins are unchanged and green per the report.
  6. A stored drivers row at the admin PATCH door: a label/active-only edit is spared (RECORD_JUDGE_EXEMPT_PATCH_KEYS, :930-940); an edit touching config/driver/pool/external or carrying a secret now meets assertValidRecord and the spec's sentence. RIGHT as a derived narrowing on the PATCH door, correctly named in the PR body and the out_of_scope note; same posture as every other spec refusal on a stored row.

Public-surface changes:

  1. New export ROUTE_SHADOWED_DATASOURCE_NAMES (Object.freeze(['drivers'] as const), type readonly ['drivers']) from @objectstack/spec/data (data/index.ts:302 re-exports the module); api-surface/data.json and export-origins/data.json carry it. RIGHT. It is a new public name and so needs the maintainer's nod (③).
  2. @objectstack/service-datasource: ROUTE_SHADOWED_DATASOURCE_NAMES keeps its declared type readonly string[] and is now the spec's array; DRIVER_CATALOG_SEGMENT is typed (typeof SPEC_...)[number], i.e. 'drivers', so the registrar (admin-routes.ts:696) mounts the catalog from a member of the spec list and a segment the spec does not reserve does not compile. RIGHT: one list, both halves, and the drift pin (__tests__/datasource-route-reserved-names.test.ts:80) still compares the list to the literals actually mounted beside a same-method /:name, in both directions.
  3. DatasourceSchema.name's describe text changes, so content/docs/references/data/datasource.mdx is regenerated; the text in the mdx is byte-identical to the describe string. RIGHT.

② Semver level

Changeset .changeset/29308-datasource-route-shadowed-name.md: @objectstack/spec: minor, @objectstack/service-datasource: patch. RIGHT. The kind is Metadata-protocol: narrowed (a value refused), which grades at least one moved package minor+ (spec, where the narrowing is); service-datasource's published source moves (it imports the spec list) and the narrowing is not its own, so patch is the honest level; Check Changeset is green on the head, LEVEL AXIS accounted. The changeset body carries both declaration lines, a BREAKING banner, the FROM → TO remedy (rename, re-point datasource/datasourceMapping), and exactly one marker adr-0087: registered datasource-name-route-shadowed-refused, whose id matches the entry's id: line (the gate's id regex at check-adr-0087-registration.mjs:2073). PR title carries fix(spec)!:.

Clause-②: no (narrowing) on the PR body and the changeset, matched by Metadata-protocol: narrowed. RIGHT. The pair admits one arm; the diff narrows a published accept set, which is the ADR-0087 breaking signal, and the additive public export is carried by the minor level rather than a yes arm (check-changeset-no-major.mjs, level axis: Clause-② keeps its meaning and loses its level coupling; a narrowed kind cannot sit beside a line without (narrowing)). The delta anticipated the question ("if the export gate reads yes, follow the gate"); the gate read it as accepted.

③ Boundary flags

Dev flags (os-dev-report 6579884811):

  • open_questions 1, the export name ROUTE_SHADOWED_DATASOURCE_NAMES (A) vs RESERVED_DATASOURCE_NAMES (B). Judgment: A is right; B would read as if default belonged in it, and default must keep parsing. ESCALATED: a new public name needs the maintainer's approval before landing (standard order §5; the PR body's 需要你做的 says so). The seat asks; this record does not stand in for that nod.
  • open_questions 2, DRIVER_CATALOG_SEGMENT as a typed literal rather than an index into the tuple. ANSWERED: A suffices. The type binds the segment to the spec list at compile time, and the unchanged drift pin binds the list to the mounted routes at test time; a second public name would add nothing enforced.
  • deviations[0], content/docs/data-modeling/drivers.mdx outside the claim's surface. ANSWERED: the claim comment 6578377666 already names that sentence in its file surface ("amended on the dev's report"); nothing to split.
  • deviations[1], service-datasource patch. ANSWERED in ②.
  • deviations[2] and [3] (no MCP writes, no PR-activity subscription). Procedural, consistent with the standing order; nothing to judge.
  • out_of_scope_findings (a stored drivers row can no longer take a config/driver/pool/external edit at the admin door). Read and agreed: the expected posture of a spec refusal on a stored row; the PR body documents it; no carrier owed.

Reviewer's own check, asked by the brief: does any ui/** mirror of DatasourceSchema drop the new name refusal (the objectui#7715 class)? NO. git grep DatasourceSchema under ui/ at the head finds only direct imports from @objectstack/spec/data (tests in app-shell), prose, and the parity test that lists DatasourceSchema as a name the spec owns and ui never mirrors (ui/packages/types/src/__tests__/report-chart-query-spec-parity.test.ts:742). There is no derived zod twin to drop the check. The Console's datasource page has a client-side convenience gate (DatasourceResourcePage.tsx:491, the identifier regex alone), which lets drivers reach the server, where the create door's 400 sentence comes back as the save-failed toast; that is the author's words rendered, not a silent accept, and it is not a schema mirror. No finding on this PR.

④ Design judgment

packages/spec/src/data/datasource.zod.ts, against the ruling (seat's call on the card: the reservation lives in the spec, from one exported constant the routes also mount from) and its first consumers (service-datasource registrar and create door; the metadata save door; stack.zod.ts):

  • Holds. One frozen constant beside the schema; the registrar's catalog segment is typed against it and the create door's membership test reads it; the drift pin keeps the list equal to the mounted literals in both directions; DatasourceSchema.name refuses each entry with one issue at name, which the save door turns into 422 INVALID_METADATA and defineStack into STACK_SCHEMA_INVALID at datasources.N.name (pinned). The host's default row and the draft-probe literal test are correctly left out, with the reason stated where the next reader will look. The refusal is a pattern, so the published JSON Schema carries it and no refinement baseline moves.
  • Tried to break: (i) Drivers or DRIVERS — met by the identifier pattern first, one issue, control pinned. (ii) A name containing the word (drivers_archive, _drivers, my_drivers) — the lookahead is anchored ^(?!(?:drivers)$), so they pass; pinned. (iii) A future second entry that is not a snake_case identifier would need regex escaping — the test pins every entry against the name pattern, so the unescaped join is safe by construction. (iv) A future literal mounted by the registrar without a reservation — the drift pin fails; a future spec entry no route shadows — the drift pin fails the other way. (v) The create door double-refusing — assertValidName runs first and throws; the schema is never reached for that input.
  • should-fix (not a FAIL): ROUTE_SHADOWED_NAME_REFUSAL is one static sentence built for the WHOLE list ("Datasource name 'drivers' is reserved ... at /api/v1/datasources/drivers ..."), not for the offending input. With one entry it reads exactly right; the day a second entry lands the sentence becomes "Datasource name 'drivers', 'x' is reserved ... at /api/v1/datasources/drivers and /api/v1/datasources/x", and the per-name test (startsWith("Datasource name '${name}' is reserved: ")) fails on its own. zod 4's .regex() accepts a message FUNCTION of the issue, which can name the offending input and would keep the sentence per-name as the list grows. Cosmetic today; worth fixing while the list has one member.
  • Observation, no action owed: the admin create door's own sentence ("at '/datasources/drivers'") and the spec's sentence ("at /api/v1/datasources/drivers, ahead of the by-name read ...") are two wordings of one refusal. The delta accepted "same message family" and the door's pin binds its wording, so this is recorded, not asked for. The NAME_RE docblock in datasource-admin-service.ts:57 ("mirrors DatasourceSchema.name") now describes only the first of two patterns; assertValidName covers the second by the list, so nothing is dropped.

packages/spec/src/migrations/entries/semantic/18.datasource-name-route-shadowed-refused.ts, against its first consumer (the ADR-0087 registry generator and the changeset marker) and its execution plan (the sibling 18.stack-datasource-host-reserved-name-refused.ts it is modelled on):

  • Holds. Shape matches SemanticMigration (id, surface, replacement, reason, acceptanceCriteria; no conversionIds, since it judges no mechanical edit; no relevance question, like its sibling). The 18. prefix is the protocol major the generator reads off the filename (build-migration-registry.ts:200), and spec is at 18.2.0. The id is registered by the changeset marker, and check:adr-0087-registration read it as "registered (new here)". The reason states the measured-producers count, and the acceptanceCriteria are a real "done when" (no issue at name or datasources.N.name; the save door answers 200 for the renamed datasource; a stored row loads valid: false and its next save is refused; label/active edits and removal still work), each of which the cited code and the new tests make true. "NOT mechanically convertible" is right: the new name and the objects that route to it are the author's call, exactly as the sibling says of default.
  • Tried to break: every door the surface names (defineDatasource, a stack's datasources, os validate, os build, the metadata save door) reaches DatasourceSchema in the cited code; the claim "the one list the admin routes mount from" is true at the head (the registrar mounts from a member of the spec list). Nothing in the entry overstates the tree.
  • No finding.

Must-fix: 0. Should-fix: 1 (the per-list refusal sentence).

Implemented-by: claude/project-thread-rx0qzr
Reviewed-by: session_01ADHkmSduNGC2SZXszyrNut

VERDICT: PASS

Build the sentence from the input (zod 4 error function) instead of once for
the whole list, pin that it names only itself, and correct the NAME_RE
docblock in the admin service, which describes the first of the two patterns.

Claude-Session: https://claude.ai/code/session_01ADHkmS1mEJA9L4pTrajNhU
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e5f6120b7f55592da59b6e9d2b7f710fe25be6a8
Local-runs: none

This is a DELTA record for the push after the PASS record 6580118235 (head 4df813f3). The delta 4df813f3...e5f6120b is one fast-forward commit across 3 files, and it is the record's Should-fix done as asked. In packages/spec/src/data/datasource.zod.ts the static ROUTE_SHADOWED_NAME_REFUSAL (one sentence for the whole list) becomes routeShadowedNameRefusal(issue), passed as .regex(NOT_ROUTE_SHADOWED_NAME, { error: routeShadowedNameRefusal }). It builds the sentence from issue.input, so it names the offending name and its route only. The pattern itself, and so the published JSON Schema's second pattern, is unchanged; the describe text still lists the whole list, which is right for a description. datasource-route-shadowed-name.test.ts gains one per-name case asserting every quoted name and every literal /api/v1/datasources/<x> route in the message is that one name (8 cases now). The NAME_RE docblock in datasource-admin-service.ts now says it is the first of the two patterns and that assertValidName covers the second from the spec list, which closes the record's observation. No export, changeset, migration entry or door moved, so every judgment in 6580118235 (①–④, @objectstack/spec: minor, @objectstack/service-datasource: patch, Clause-②: no (narrowing), Metadata-protocol: narrowed) carries to this head unchanged. ci-failure.mjs --sha e5f6120b…: GREEN, 65 check-runs, none failed. The export name ROUTE_SHADOWED_DATASOURCE_NAMES is with the maintainer (needs-user-decision, comment 6580012312).

Implemented-by: claude/project-thread-rx0qzr
Reviewed-by: session_01ADHkmSduNGC2SZXszyrNut

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Head-sha: e5f6120b7f55592da59b6e9d2b7f710fe25be6a8 · read 2026-11-02T15:20Z

维护者速读

  • 改了什么:名叫 drivers 的数据源现在在所有解析数据源的入口都被拒收(代码声明、defineStack、os validate、元数据保存门 422),不再只在管理接口的新建门拒收。保留名单搬进 spec,成为唯一一份,管理接口的路由和新建门都读它。
  • 为什么改:GET /api/v1/datasources/drivers 是驱动目录,叫 drivers 的数据源永远按名字读不到;新建门拦住了,但代码和元数据保存门还能造出这种读不到的数据源。
  • 风险与回滚:这是收窄,叫 drivers 的数据源再保存会被拒;仓内作者写的此名数据源为 0。已存的行照常加载(valid: false),仍可列出、删除、只改标签。回滚就是 revert 本 PR。
  • 先看:packages/spec/src/data/datasource.zod.ts 的 ROUTE_SHADOWED_DATASOURCE_NAMES 和 name 上的第二条 pattern。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/data/datasource-route-shadowed-name.test.ts,预期 8 项全绿。
  • 需要你做的:新公开导出名 ROUTE_SHADOWED_DATASOURCE_NAMES 待总监裁定(needs-user-decision,分析见 6580012312)。裁定保留后席位落地;改名则开发改名、席位补 delta 再落地。

席位意见:可落地,只等名字。契约复审在 4df813f 判 PASS(6580118235),在现 head 的 delta 判 PASS(6580844367),CI 全绿。

席位核对 — ACCEPT · domain:spec#2 · head e5f6120b

  1. Shape: draft, base main, Fixes #29308, title fix(spec)!:. Clause-②: no (narrowing) and Metadata-protocol: narrowed sit at line start in the body and the changeset (@objectstack/spec minor, @objectstack/service-datasource patch, BREAKING banner, one adr-0087: registered datasource-name-route-shadowed-refused marker). 10 files, +282/−16, inside claim 6578377666. No governed path.
  2. Premise measured: on main a drivers datasource parses through DatasourceSchema, defineStack and the save door, while only the admin create door refuses it. On the head the new test refuses it with one invalid_format issue at name, defineStack answers STACK_SCHEMA_INVALID / 422 at datasources.0.name, and the published JSON Schema rejects it; the controls (driver, drivers_archive, _drivers, test, default) still parse.
  3. Gates: ci-failure.mjs --sha e5f6120b… GREEN: 65 check-runs, none failed.
  4. Accepted deviations: content/docs/data-modeling/drivers.mdx corrected ("reserved at the admin API only" became false; named in the claim). Forced test edits: none.
  5. Out of scope: a stored drivers row can no longer take a config/driver/pool/external edit at the admin PATCH door; the PR body documents it as the normal posture of a spec refusal on a stored row, no carrier owed.
  6. Landing: not Tier H and under 3000 lines. It lands only after the name ruling: if the name stays, the seat removes needs-user-decision and lands with pr_ready and auto-merge; after merge, a landing note on 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 and pm:dispatched removed.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Withdrawn: the name question on this PR. The pm-dispatch skill carries no rule that a new public name needs the maintainer's approval; that line lived only in this seat's standard order. Under SKILL.md 〈升级与决策〉 a name escalates only when the options truly diverge on product semantics or contract shape and no existing norm decides; here the dev's name is fit and no fork exists, so it is the seat's call: the name stays as built. needs-user-decision is removed and the seat lands this PR on its ACCEPT.

Prompted by the maintainer's question at 2026-11-02T17:03Z.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 17:15
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 2, 2026 17:15
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 41489734213 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (2/8) — 失败步骤: Run this shard's tests

    @objectstack/spec:test:repo:  FAIL   repo  scripts/strictness-ledger-doc.test.ts > the ledger and the generated counts agree > is checked in current — every shard on disk equals a fresh render, and no
      ↳ 失败原因: @objectstack/spec:test:repo: AssertionError: expected Map{ …(14) } to deeply equal Map{ …(14) }
    @objectstack/spec:test:repo:  FAIL   repo  scripts/strictness-ledger-doc.test.ts > the ledger and the generated counts agree > the read-time totals equal the sums of the shards' own rows
      ↳ 失败原因: @objectstack/spec:test:repo: AssertionError: expected 'untriaged: 1293 object site(s) across…' to be 'untriaged: 1292 object site(s) across…' // Object.is equality
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 7 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 41491867212 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (2/8) — 失败步骤: Run this shard's tests

    @objectstack/spec:test:repo:  FAIL   repo  scripts/strictness-ledger-doc.test.ts > the ledger and the generated counts agree > is checked in current — every shard on disk equals a fresh render, and no
      ↳ 失败原因: @objectstack/spec:test:repo: AssertionError: expected Map{ …(14) } to deeply equal Map{ …(14) }
    @objectstack/spec:test:repo:  FAIL   repo  scripts/strictness-ledger-doc.test.ts > the ledger and the generated counts agree > the read-time totals equal the sums of the shards' own rows
      ↳ 失败原因: @objectstack/spec:test:repo: AssertionError: expected 'untriaged: 1297 object site(s) across…' to be 'untriaged: 1296 object site(s) across…' // Object.is equality
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 14 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit c9aaee9 Nov 2, 2026
69 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-rx0qzr branch November 2, 2026 18:18
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 protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(data): DatasourceSchema.name accepts drivers (and the metadata save door stores it), a name the datasource admin routes can never serve by name

1 participant