Skip to content

fix(service-datasource)!: Test connection and Save refuse a SQLite filename that is neither :memory: nor a usable file path - #30395

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-541rod
Nov 5, 2026
Merged

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

维护者速读

  • 改了什么: 新建/编辑 SQLite 数据源时,文件名既不是 :memory: 也不是可用文件路径(例如 :memory:/srv/data/parts.db、空字符串、建不出来的路径),“测试连接”现在判失败,保存也拒收,提示里写出这个值和原因。
  • 为什么改: 对话框里 :memory: 是真实值,用户在后面接着打路径就成了 :memory:/…,测试连接却显示成功;存下去后原生 SQLite 每次查询都报错,sqlite-wasm 则开了个内存库,写进去的数据重启就没了。管理员会以为连上了公司数据库。
  • 风险与回滚: 只收紧管理入口的这三类文件名;:memory:、已存在的数据库文件、还不存在但能创建的路径都照旧。已存的旧数据源照常启动,只有改它的配置时才会被判。回滚即撤 PR。
  • 先看: packages/services/service-datasource/src/sqlite-database-filename.ts(判定本身);datasource-admin-service.ts 的 assertValidConfig(测试、新建、更新三处共用的那一处)。
  • 两分钟验证: pnpm --filter @objectstack/service-datasource exec vitest run --maxWorkers=2 src/__tests__/sqlite-filename-neither-memory-nor-path.test.ts,预期 14 个全绿。
  • 需要你做的: 无,席位落地(收窄,合并前需合同审查)。对话框把 :memory: 做成占位符而非真实值,是 ui 车道的另一半,卡留开。

Refs #30331 (server half)

Clause-②: yes (narrowing) — POST /api/v1/datasources/test, POST /api/v1/datasources and a PATCH /api/v1/datasources/:name that touches config or driver refuse a sqlite / sqlite-wasm config.filename that is empty, starts with : without being exactly :memory:, or names a path that can be neither opened nor created. The test answered ok: true for the first two and the save stored all three; both now answer with the value and the reason (result.ok: false from the test, 400 DATASOURCE_ADMIN_ERROR from the save), the envelope every invalid config already gets.
Metadata-protocol: none
Skills-docs: none — skills/** and content/docs/** teach filename as "a database file path, or :memory:" (content/docs/references/data/driver-sqlite.mdx, content/docs/data-modeling/drivers.mdx), which is what the doors now enforce; no line turns false. follow-up owed — packages/spec/src/data/driver/sqlite.zod.ts types filename as any placeholder-free string, so the build door and PUT /api/v1/meta/datasource/:name still accept these values; the refinement belongs in the spec.

What changes

Measured first, on main 8589756, through the real POST /api/v1/datasources/test with config.filename: ':memory:/tmp/x30331/parts.db': sqlite answered { ok: true, latencyMs: 13 } and sqlite-wasm { ok: true, serverVersion: '3.49.1' }. Why:

  • Every layer reads a name that starts with : as fileless: driver-sql's resolveSqliteAbsentFileTarget and sqliteFilename, the wasm connection's isEphemeral. So the probe's missing-file refusal (datasources: the draft probe POST /datasources/test creates the SQLite database file on disk although nothing is saved #27512) skipped it, and SqlDriver.connect() made no directory for it.
  • sqlite (better-sqlite3) does not treat it as in-memory (only exactly :memory: is). It opens it as a relative path under the server's working directory, whose :memory:/… directory does not exist. connect() only warns (Failed to set PRAGMA auto_vacuum), and checkHealth() answers false without throwing, which the probe does not read as a failure.
  • sqlite-wasm opens an ephemeral in-memory database.

What a saved datasource with that value reads (not measured by the card; measured here through the default factory, connect, write, disconnect, connect again):

  • sqlite: every statement fails with Cannot open database because the directory does not exist; health false. Nothing is written to disk.
  • sqlite-wasm: health true; a table created and a row inserted read back in the same connection; after reconnecting, no such table. Writes succeed and are lost at the next connect or restart, with no error.

The fix, in the one place the test and both save doors pass through. DatasourceAdminService.assertValidConfig already judged config against the driver contract for testConnection, createDatasource and updateDatasource (when the patch touches config or driver). It now also asks sqliteDatabaseFilenameIssue (new, sqlite-database-filename.ts) once the contract has no issues, and becomes async. For sqlite / sqlite-wasm (via resolveDriverId) the filename is usable when it is exactly :memory: or absent (the schema's default), or a path that driver-sql's write-free judgement (resolveSqliteAbsentFileTarget(…, 'empty-in-memory')) does not refuse. Refused, each with a config.filename: line under Invalid configuration for driver '…':

  • empty: config.filename: '' is empty and names no database file. …
  • :-prefixed but not :memory:: config.filename: ':memory:/…' is neither ':memory:' nor a file path. A name that starts with ':' is not read as a file, so this datasource would store nothing: …
  • a path that can be neither opened nor created: config.filename: + driver-sql's SqliteDatabaseFileError text, which names the file and the operating system's answer.

Same envelope and code as every invalid config (400 DATASOURCE_ADMIN_ERROR from save; result.ok: false from the test). No new error code.

Unchanged: exactly :memory: tests ok and saves; an existing database file tests ok and saves; a creatable path that does not exist yet is refused by the test (which never creates it, #27512) and accepted by Save, which creates it on connect. turso names its target as a file: url and is not judged here. The probe in datasource-admin-plugin.ts, the driver factory, and the boot restore are not touched: a stored row is not re-judged at boot.

Tests

packages/services/service-datasource/src/__tests__/sqlite-filename-neither-memory-nor-path.test.ts, 14 cases (7 per driver, sqlite and sqlite-wasm), through the real HonoHttpServer, the real admin routes, the real DatasourceAdminServicePlugin (its probe and its service) and the default driver factory, with a map-backed metadata service so a save is observable:

  • :memory:/… is refused by Test connection, naming the value and why; nothing is written under the test root.
  • :memory:/… is refused by create (400 DATASOURCE_ADMIN_ERROR, nothing stored) and by an update of a saved :memory: datasource (stored value unchanged).
  • a path under a regular file is refused by the test and by create; nothing written.
  • an empty filename is refused by the test and by create.
  • controls: exactly :memory: tests ok and saves (201); an existing database file tests ok and saves; a creatable missing path is refused by the test with "does not exist", nothing created, and is saved.

Ablation (sqliteDatabaseFilenameIssue short-circuited to undefined, source imported by relative path so no dist/ leg): 8 failed / 6 passed, the 8 refusal cases red and the 6 controls green. Restored with git checkout, then 14/14.

Acceptance notes

  • ui residue (not in this PR). The New datasource dialog still holds :memory: as the Filename field's value instead of a placeholder (ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.tsx). With this PR the mistake it invites is refused with a message, instead of passing. The card stays open for that half.
  • The probe ignores a false health check. DatasourceAdminServicePlugin.probe awaits ping() (the handle's checkHealth) and reports ok: true whatever it answers. That is how the native :memory:/… draft passed; this PR closes the input, not that reading. No other public input reaching it was measured.
  • Stored rows. A datasource already saved with such a filename keeps loading at boot as it did. An edit that touches its config must fix the filename; a label / active-only edit is not judged, so it can still be taken out of service.
  • Save judges the server's filesystem. An unusable path is judged where the server runs, write-free, the same judgement the test and os migrate plan already make.
  • Assignee and reviewers: none set. The dispatch forbids GitHub MCP writes and asks for no review request, so the session's usual reviewer step is left to the seat.

Verification

Head cca010a64, the merge of origin/main into the branch; the merge touches none of this PR's 4 files.

  • New suite at head: 14 passed. Full @objectstack/service-datasource suite: 72 files, 1183 passed. pnpm --filter @objectstack/service-datasource run typecheck exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands derived 61 families; each ran at head with its exit recorded before any pipe: 61 of 61 exit 0 (check-plugin-teardown-shape --self-test after fetching its fixture commit, check:dual-build-cjs-loads after the full build). --ran: 61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN, exit 0.
  • pnpm lint exit 0.
  • turbo run build --filter='!@objectstack/docs' 119/119 under the verify lock (VERDICT command-exit 0).
  • Full dogfood battery (narrowing arm), pnpm --filter @objectstack/dogfood test under the verify lock: VERDICT command-exit 0, Test Files 419 passed / 3 skipped (422), Tests 3492 passed / 36 skipped (3528).
  • Ablation: see Tests.
pnpm --filter @objectstack/service-datasource exec vitest run --maxWorkers=2 src/__tests__/sqlite-filename-neither-memory-nor-path.test.ts

Expect 14 passed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Utp6Cb83ajcseWprEF9C9g

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

7 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 3 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 8fad7b19389c913efbdf00a24965bec024509d60 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6acaa8e7ce86f83ddd7bf2b3090272617a78b8aa — the merge of head cca010a640e31e8591d1aea9caeca53ba1a45fc2 into base 8fad7b19389c913efbdf00a24965bec024509d60, 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 6acaa8e7ce86f83ddd7bf2b3090272617a78b8aa && git checkout 6acaa8e7ce86f83ddd7bf2b3090272617a78b8aa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8fad7b19389c913efbdf00a24965bec024509d60 cca010a640e31e8591d1aea9caeca53ba1a45fc2 && git checkout -B drift-repro 8fad7b19389c913efbdf00a24965bec024509d60 && git merge --no-ff cca010a640e31e8591d1aea9caeca53ba1a45fc2

node scripts/docs-audit/affected-docs.mjs --json 8fad7b19389c913efbdf00a24965bec024509d60

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

Contract-review-claim: session_01Utp6CZMkHvuDeVYgTD73qF · head cca010a · 2026-11-05T09:35Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: cca010a640e31e8591d1aea9caeca53ba1a45fc2
Local-runs: none
Scope: packages/services/service-datasource/src/sqlite-database-filename.ts, packages/services/service-datasource/src/datasource-admin-service.ts, packages/services/service-datasource/src/tests/sqlite-filename-neither-memory-nor-path.test.ts + packages/services/service-datasource/src/datasource-admin-plugin.ts (probe, probedDatabaseFile, absentDatabaseFileRefusal), packages/services/service-datasource/src/default-datasource-driver-factory.ts (toHandle), packages/services/service-datasource/src/sqlite-driver-fallback.ts (resolveSqliteDriver, isSqliteFileOpenFailure), packages/services/service-datasource/src/admin-routes.ts (the error-code map), packages/drivers/driver-sql/src/sql-driver-top-level.ts (resolveSqliteAbsentFileTarget), packages/drivers/driver-sql/src/sqlite-database-file.ts (probeAbsentSqliteDatabaseFile, SqliteDatabaseFileError), packages/drivers/driver-sql/src/sql-driver-database-helpers.ts (sqliteFilename), packages/drivers/driver-sql/src/sql-driver.ts (applyAutoVacuumIncremental, checkHealth), packages/drivers/driver-sqlite-wasm/src/wasm-connection.ts (isEphemeral), packages/spec/src/data/driver/sqlite.zod.ts, packages/spec/src/data/driver/config-registry.zod.ts (resolveDriverId, validateDriverConfig), content/docs/references/data/driver-sqlite.mdx, content/docs/data-modeling/drivers.mdx. Not reviewed: the changeset (read for ② only); the four changeset deletions and the 32 other paths in the raw diff are main having moved under the branch, and git diff of main at the head's merge-base shows the PR owns exactly four files.

Inputs: card #30331 (body, triage 6652062778, claim 6652710575, dev report 6654592689), PR #30395 (body, file list, net diff against main 8fad7b1 = the head's merge-base), the check-runs on the head.

① Derived judgments

  • Accept-set narrowing at three admin doors, right. DatasourceAdminService.assertValidConfig is the one judgement testConnection, createDatasource and updateDatasource (when the patch touches config or driver) already share; sqliteDatabaseFilenameIssue is asked there, only once the driver contract has no issue, only for resolveDriverId(driver) ∈ {sqlite, sqlite-wasm} (so the sql, sqlite3, better-sqlite3, wasm-sqlite, wasm spellings too, the same set the probe's probedDatabaseFile reads). Refused: ''; a :-prefixed name other than exactly :memory:; a path probeAbsentSqliteDatabaseFile throws for (not stat-able for a reason other than ENOENT, a dangling link whose end cannot be made, a nearest existing level without W_OK|X_OK). Each refusal names the value and the reason under the existing Invalid configuration for driver '…' first line.
  • Kept answers, read in the code, right. Exactly :memory: and an absent filename (the schema's default) return before any filesystem read. An existing file: statSync answers, 'exists', no refusal. A creatable missing path: resolveSqliteAbsentFileTarget returns {openedEmptyInMemory: true} without throwing, so Save still accepts it and creates it on connect, and the test still refuses it with the unchanged datasources: the draft probe POST /datasources/test creates the SQLite database file on disk although nothing is saved #27512 sentence from the probe, which runs after this check. A relative path resolves against the server's cwd, as the spec's own docblock says it does.
  • Not widened past the card, right. turso/libsql is not judged (it names a url the libSQL driver parses; its file: path keeps the probe's datasources: the draft probe POST /datasources/test creates the SQLite database file on disk although nothing is saved #27512 treatment). A remote url under the sqlite id, or a file: url under it, is a relative path to better-sqlite3 (no URI mode), judged exactly as the probe judged it on main: absent-creatable, so test refuses "does not exist" and Save accepts, unchanged. A read-only directory is refused at Save where SqlDriver.connect() already refuses it at pool registration (driver-sql: the read-only probe's absent-file test (existsSync) reads an unopenable SQLite path as absent, so os migrate plan opens an empty in-memory database, reports the datasource connected, and plans DDL into a file that cannot exist #24961); the row that used to be stored could never serve.
  • Public surface: none. assertValidConfig is private (its d.ts line carries no signature), sqlite-database-filename.ts is not re-exported from index.ts, TestConnectionResult and the route envelope are unchanged. @objectstack/driver-sql is a declared dependency and the dynamic import is the plugin's existing idiom.
  • Envelope, right (brief d). admin-routes.ts maps every Error the datasource-admin service throws to 400 DATASOURCE_ADMIN_ERROR; the package has no narrower config code (the only other is EXTERNAL_DATASOURCE_ERROR), and every invalid config already lands here, so a new code would split one refusal across two.
  • Stored rows and boot (brief c), right. assertValidConfig has three callers, all admin-door writes; restoreRuntimeDatasources, rehydratePools, the env/boot datasource and the driver factory never reach it, so a row stored with such a value loads as before. A label/active-only PATCH skips it (the datasource.config 至今无人校验:驱动 configSchema 是声明但完全惰性的(ADR-0049 enforce-or-remove,#4001 收尾发现) #4410 exemption), so it can still be taken out of service; a PATCH that touches driver alone judges the merged config, which is the existing "touches the pairing" rule, not a new one.
  • Test file. Drives the real HonoHttpServer, real routes, real plugin (probe included) and the default factory with a map-backed metadata service; 7 cases per driver, the 4 refusals asserting the value and a reason regex, the 3 controls asserting the kept answers, plus readdirSync(root) / existsSync that nothing was created. The assertions discriminate the change on their own; the body's ablation count is not relied on.

② Semver level

  • Changeset .changeset/30331-sqlite-filename-neither-memory-nor-path.md: @objectstack/service-datasource: patch, the only package the net diff moves. Right: a non-protocol narrowing ships patch with its BREAKING banner (present) and its ADR-0087 disposition (adr-0087: not-required (no-migration-prescription), one marker, with a reason that holds: no key moves, nothing at rest is rewritten).
  • Clause-②: line, PR body and changeset, identical and well-formed: Clause-②: yes (narrowing) — …, naming the three doors, the three refused shapes and the unchanged envelope. Metadata-protocol: none is right as the tree stands: SqliteConfigSchema and the meta door are untouched, so what a metadata author can write has not changed; it becomes narrowed in the spec follow-up (④ S1), not here. Skills-docs: none holds: both docs pages teach filename as "Database file path, or :memory:", which is the set the doors now enforce.
  • Check Changeset on the head: success.

③ Boundary flags

  • open_questions: [] in the dev report; security_findings: none. Nothing to escalate.
  • Dev flag "ui residue": the dialog's :memory: value instead of a placeholder is the domain:ui half (triage 6652062778); the card stays open, the PR carries Refs, not a closing keyword. Answered.
  • Dev flag "the probe ignores a false health check": confirmed in the tree (toHandle maps ping to driver.checkHealth(), a boolean the probe awaits and drops; in production resolveSqliteDriver hands back the native driver unprobed and connect() swallows the code-less better-sqlite3 error). This PR closes the only public input measured to reach it; see ④ S2 for the follow-up.
  • Dev flag "spec SqliteConfigSchema.filename still accepts the value at the build and meta doors, follow-up owed": confirmed (placeholderFree(z.string()).default(':memory:'), packages/spec/** was outside this seat's file surface). See ④ S1.
  • Dev flag "Save judges the server's filesystem, write-free": right, and the same host the save's tryRegisterPool connects on.
  • Dev flag "assignee and reviewers none": the seat's.
  • Lint · docs & code guards (runtime strings carry no tracker number: the three new refusal strings carry none, the [#30331] citations are in docblocks) and the rest of the derived families are read off the check-runs below.

④ Design judgment

The narrowing read against the published spec on main: SqliteConfigSchema.filename is "Database file path, or :memory: for an ephemeral in-process database" (docblock, describe, both docs pages). :memory:/…, '' and a path the operating system refuses to open or create are in none of those three classes, and no layer delivers them: driver-sql's sqliteFilename and resolveSqliteAbsentFileTarget read a :-prefixed name as fileless and make no directory for it, better-sqlite3 opens it as a relative path whose directory does not exist, the wasm connection's isEphemeral opens an ephemeral database over it. So the PR makes the spec's own refusal real at the three doors that accepted the value; it takes no declared capability away at the consumer. Attempts to break it, each read in the cited code:

  • :memory:?cache=shared, :other:: refused. Neither was a delivered capability (better-sqlite3 runs without URI mode; wasm treated every :-name as a nameless ephemeral database), and the spec names one spelling.
  • An existing directory, or a file this process may not read, as filename: stat answers, so this check passes it; connect() then raises SqliteDatabaseFileError on SQLITE_CANTOPEN and the probe answers ok: false. No false pass remains through this gate for a path whose refusal carries a code.
  • Order of judgement: the contract first (strict keys, placeholders), the filename only when the contract is clean, so a refusal never carries two reasons for one key, and a non-string filename keeps the contract's own line.
  • Update with a redacted config: restoreRedactedConfig runs after this judgement, but filename is never a redacted key, so the judged value is the stored one.
  • Multi-replica: the save door judges the replica it runs on, which is the one whose tryRegisterPool opens the file next; the probe already made that choice.

Findings:

  • S1 (should-fix, follow-up card before the ui half lands): the '' and :-prefixed-not-:memory: arms are expressible in the spec and belong on SqliteConfigSchema.filename / SqliteWasmConfigSchema.filename (a refinement with the same prescription), so the build door and PUT /api/v1/meta/datasource/:name refuse what the admin doors now refuse; until then the two doors store a row the admin door's update will not accept back. The dev names it in the PR body, the changeset and the report's out_of_scope_findings (class b) as owed to the seat; the filesystem arm stays in the runtime, where it must. Not a must-fix: packages/spec/** was outside this seat's surface, and the card's Expected names the test and the save doors, both closed here.
  • S2 (should-fix, follow-up card): DatasourceAdminServicePlugin.probe should read checkHealth()'s boolean (ping is that same call through toHandle) and answer ok: false when it is false; today a connect that only warns passes. After this PR no measured public input reaches it (the code-less better-sqlite3 "directory does not exist" needs a :-name, which is now refused at the door before the probe), so it is a latent reading, not a hole the card measured.
  • No must-fix.

Implemented-by: claude/project-thread-541rod
Reviewed-by: session_01Utp6CZMkHvuDeVYgTD73qF

VERDICT: PASS

Gate verdicts, the check-runs on cca010a640e31e8591d1aea9caeca53ba1a45fc2 read at 2026-11-05T09:58Z: 48 check-runs, all completed, 35 success / 13 skipped / 0 failed. Green: Build Core; Test Core (8 of 8 shards, and the aggregate); Type Check · workspace / source gates / consumer gates / debt ledger; TypeScript Type Check; Lint · ESLint / docs & code guards / repo & tooling gates / PM dispatch-gates / CI, release & engine gates / scoped (query-options erasure, slot lookup & corpus, tooling self-tests); Lint & Repo Gates; Check Changeset; Check PR Size; PR-body guards; Governed Surface Queue Guard; Flag docs affected by code changes; Dogfood Regression Gate; Test UI; Live E2E UI; Auto Label; filter; Plan Test Core shards. Skipped (path-gated or opt-in, no ui/, docs or temporal path in the diff): Lint UI, Type Check UI, Build UI Deps, Build & E2E UI, UI Published Artifacts, Test UI shards, Build Docs, Dogfood Verify CLI, Dogfood Regression Gate shards, Temporal Conformance, Packed-tarball smoke, 18.x upgrade harness, Schedule gate.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

席位意见:ACCEPT(#30331 服务端一半)。数据源「测试连接」「保存」现在拒收既不是 :memory: 也不是可用文件路径的 SQLite 文件名(空串、:memory:/… 这类冒号开头名、服务器打不开也建不了的路径),拒绝语句写出值和原因。:memory:、已有文件、可新建的路径照旧。界面一半(对话框里 :memory: 当默认值而非占位)留在卡上,PR 用 Refs。

席位核对

Head-sha: cca010a640e31e8591d1aea9caeca53ba1a45fc2 · read 2026-11-05T10:01Z

Item Reading
Shape Draft → main, author objectstack-fleet[bot] (relay pr_create); attribution block, then signature line, Refs #30331, Clause-② / Metadata-protocol / Skills-docs at line start. 4 files, +320/−9. ⛔ packages/spec/** and ui/** untouched.
Ruling Card #30331's Expected (test and save doors refuse the value); triage 6652062778 (server half here, ui half stays on the card); claim 6652710575.
Gates (check-runs on this head) 48 completed: 35 success, 13 skipped (path-gated ui/docs/opt-in), 0 failed. Build Core, Test Core 8/8, every Type Check and Lint lane, Check Changeset, Governed Surface Queue Guard, Dogfood Regression Gate green.
Contract review CONTRACT_REVIEW_TIER record 6655074792 on this head: PASS, no must-fix. Narrowing lands at the one shared assertValidConfig (test / create / update touching config or driver), only for resolveDriverId ∈ {sqlite, sqlite-wasm}, after the contract is clean; turso/libsql untouched.
Should-fixes, seat side S1 (spec refinement for '' and :-prefixed names at the build and meta doors): scope added to the spec card #30400. S2 (the probe drops checkHealth()'s boolean): filed as #30407, serial after this PR.
Semver @objectstack/service-datasource: patch with !, BREAKING banner, Clause-②: yes (narrowing), Metadata-protocol: none, one adr-0087: not-required (no-migration-prescription) marker; judged right by the record.
Landing Not governed (check-governed-merges --pr 30395: 0 of 4 paths), 329 lines ≤ 3000. ⛔ BREAKING (!): the seat does not queue it until the maintainer's own word on this landing.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

维护者速读

  1. 改了什么:新建/编辑 SQLite 数据源时,文件名既不是 :memory: 也不是可用路径(:memory:/…、空串、建不出的路径),「测试连接」判失败、保存拒收,提示写出值和原因。
  2. 为什么改:对话框里 :memory: 是真实值,用户接着打路径就成了 :memory:/…,测试却显示成功;存下后原生 SQLite 每次查询报错,sqlite-wasm 开了个内存库、数据重启就丢。
  3. 风险:只收紧管理入口的这三类文件名;:memory:、已存在的文件、可新建的路径照旧;已存的旧数据源照常启动,只有改它的配置时才会被判。
  4. 先看:service-datasource/src/sqlite-database-filename.ts(判定)和 datasource-admin-service.ts 的 assertValidConfig(测试、新建、更新共用)。
  5. 两分钟验证:pnpm --filter @objectstack/service-datasource exec vitest run --maxWorkers=2 src/__tests__/sqlite-filename-neither-memory-nor-path.test.ts,14 条全绿。
  6. 需要你做的:无,席位落地。收窄(BREAKING),合约审查 PASS 6655074792 在案;按 maintainer 13:47Z「技能不需要我审核的不应该等我」,不属 Tier H、不超 3000 行。对话框占位符是 ui 车道的另一半,卡留开。

席位意见:ACCEPT(#30331 服务端一半)。数据源「测试连接」「保存」现在拒收既不是 :memory: 也不是可用文件路径的 SQLite 文件名(空串、:memory:/… 这类冒号开头名、服务器打不开也建不了的路径),拒绝语句写出值和原因。:memory:、已有文件、可新建的路径照旧。界面一半(对话框里 :memory: 当默认值而非占位)留在卡上,PR 用 Refs。

席位核对

Head-sha: cca010a640e31e8591d1aea9caeca53ba1a45fc2 · read 2026-11-05T13:54Z

Item Reading
Shape Draft → main, author objectstack-fleet[bot] (relay pr_create); attribution block, then signature line, Refs #30331, Clause-② / Metadata-protocol / Skills-docs at line start. 4 files, +320/−9. ⛔ packages/spec/** and ui/** untouched.
Ruling Card #30331's Expected (test and save doors refuse the value); triage 6652062778 (server half here, ui half stays on the card); claim 6652710575.
Gates (check-runs on this head) 48 completed: 35 success, 13 skipped, 0 failed (re-read at landing).
Contract review CONTRACT_REVIEW_TIER record 6655074792 on this head: PASS, no must-fix. Narrowing lands at the one shared assertValidConfig (test / create / update touching config or driver), only for resolveDriverId ∈ {sqlite, sqlite-wasm}, after the contract is clean; turso/libsql untouched.
Should-fixes, seat side S1 (spec refinement for '' and :-prefixed names at the build and meta doors): scope added to the spec card #30400. S2 (the probe drops checkHealth()'s boolean): filed as #30407, serial after this PR.
Semver @objectstack/service-datasource: patch with !, BREAKING banner, Clause-②: yes (narrowing), Metadata-protocol: none, one adr-0087: not-required (no-migration-prescription) marker; judged right by the record.
Landing Not governed (check-governed-merges --pr 30395: 0 of 4 paths, re-read 2026-11-05T13:54Z), ≤ 3000 lines. BREAKING (!), and neither Tier H nor over 3000 lines, so per pm-dispatch the seat lands it: pr_ready + automerge_enable, on the maintainer's word 13:47Z 「技能不需要我审核的不应该等我」.

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 size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant