Repository navigation
fix(cloud-connection): an install-local install connects and records the package's declared datasources - #28132
Conversation
…the package's declared datasources The install door registered a package's objects and nothing of its datasources, so an object bound to one answered 404 OBJECT_NOT_FOUND and the name was not code-defined. AppPlugin's registration, code-set record and connectDeclared call move into one runtime helper, connectPackageDatasources, which the boot keeps calling and install-local now calls on install and on every rehydrate. In a running kernel a fail-fast verdict is reported (the datasource answers its retained verdict and a bound read answers 503 ERR_DATASOURCE_UNAVAILABLE) instead of thrown; a reinstall replaces, and the uninstall withdraws through the runtime.package-datasources cleanup. A package declaring a name another holder owns is refused 409 before any write. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cx4xgJksGo7iVA8c9A
…hangeset for install-local datasources Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cx4xgJksGo7iVA8c9A
📓 Docs Drift CheckThis PR changes 2 package(s): 17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 32 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 eb28a88492c50d939abd7af927ca7ab00b978620 && git checkout eb28a88492c50d939abd7af927ca7ab00b978620
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 062a2323ba363d15f507433987cca4d650979b40 dbc6f440b9aaf1b38d1d6c39442dbae91fd73683 && git checkout -B drift-repro 062a2323ba363d15f507433987cca4d650979b40 && git merge --no-ff dbc6f440b9aaf1b38d1d6c39442dbae91fd73683
node scripts/docs-audit/affected-docs.mjs --json 062a2323ba363d15f507433987cca4d650979b40
|
…narrowing The install-local door now refuses a package whose declared datasource name the environment already holds; main installed that package 200, so the changeset declares the narrowing arm with its BREAKING banner and ADR-0087 disposition. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cx4xgJksGo7iVA8c9A
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract-review-claim: The lane seat runs the |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract reviewServed-tier: Declaration at this head: the changeset ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
…pe helper, keeping the plugin's class name A static method called by the class's own name made esbuild rename the shipped class `_MarketplaceInstallLocalPlugin`, so the CLI's install-local identity no longer matched it. The two datasource methods also move above the handler binder's docblock, which they had split from its method. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cx4xgJksGo7iVA8c9A
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract-review-claim: The lane seat re-runs the |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract reviewServed-tier: Declaration at this head: the changeset ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Seat ACCEPT · PR #28132 · head
|
Requested by Elon · project thread
Fixes #28039
Clause-②: yes (widening)
Metadata-protocol: none
Skills-docs:
content/docs/data-modeling/external-datasources.mdxgains one callout under "When auto-connect fails" (an installed package's datasources are registered as code and connected at install and every restart; a failed one is installed with its failedstatus; the install answer'sdatasources;409on a held name).skills/**: none —git grep -n -i "datasource" skills/objectstack-platform/references/operations.mdhas no line on install-local and datasources; the install-local rows there (:107,:344) describe the CLI and the credential facts, which this change does not touch.维护者速读
os package install)装进运行中环境的包,现在会像启动其产物时一样,把它声明的数据源注册为代码数据源、记入代码数据源名单并自动连接;卸载时一并撤回。启动路径与安装路径共用一个 runtime 助手connectPackageDatasources。OBJECT_NOT_FOUND,后台也不把它当代码数据源。datasources字段,包声明的数据源名若已被占用(default、后台建的、别的包的)则安装 409 拒绝。回滚即还原本 PR。packages/runtime/src/declared-datasources.ts(助手本体,重点看installed分支吞掉 fail-fast 改为回报状态)、packages/cloud-connection/src/marketplace-install-local-plugin.ts(安装 4b′、重新加载、2a 冲突拒绝)、packages/runtime/src/app-plugin.ts(启动路径改为调用助手)。pnpm --filter @objectstack/dogfood exec vitest run test/install-local-declared-datasources.dogfood.test.ts,预期 8 个用例全绿:安装后绑定对象可写可读,凭据无法解析的数据源读到 503ERR_DATASOURCE_UNAVAILABLE,卸载后全部撤回。What changes
AppPlugin.start's three datasource steps — the in-memory metadata registration asorigin: 'code'stamped with the declaring package, theconnectDeclaredcall through thedatasource-connectionservice, and its fail-fast verdict — move intoconnectPackageDatasources(packages/runtime/src/declared-datasources.ts, exported from@objectstack/runtime). The boot calls it with the same lists by reference (finding: AppPlugin hands the RAW artifact bundle to job scheduling and datasource registration, so a retired duration key (jobs[].timeout, tursoconfig.timeout, memoryconfig.persistence.autoSaveInterval) is silently dropped #23029's identity pins hold), the same log lines and the same throw; its names still join the code-datasource set ininit().kernel:readyrehydrate, withinstalled: true, which differs from the boot in three ways: the names are contributed to the code-datasource set now; a fail-fast verdict is logged and reported instead of thrown (the datasource keeps its retained verdict and the engine's unavailable mark, so a bound read answers503 ERR_DATASOURCE_UNAVAILABLEnaming it); and a reinstall first withdraws what the previous version registered.runtime.package-datasourcesuninstall cleanup on the protocol's registry (the shaperuntime.package-jobsuses), so install-local'sDELETEand the protocol'sdeletePackageboth close the pools, remove the metadata registrations still stamped with the package and take the names out of the set. It acts only on what an installed package's call recorded; a boot artifact records nothing.datasources: [{ name, status }]— the connection service's ownConnectStatusnames, nothing new — omitted for a package that declares none.default, a datasource in the metadata service not stamped with this package) is refused409 RESOURCE_CONFLICTwithdetails.datasource, before any write: the connect is idempotent on the name, so it would have served the package's objects from the holder's pool, and recording the name as code would have made the holder's own datasource read-only.CodeDatasourceNamesgainsdelete, andcode-datasource-names.tsawithdrawCodeDatasourceNames(module-internal, not exported from the package); the module header now names the post-boot producer.Tests
packages/qa/dogfood/test/install-local-declared-datasources.dogfood.test.ts(new, real showcase composition, real sqlite driver): install answers both verdicts; registrations areorigin: 'code'with_packageId/_packageVersion; the code-datasource set holds both names; the admin list servesqads_storeokandqads_vaulterrorwith astatusReasonnaming the credential; write201+ read200of the bound object; the vault-bound read503 ERR_DATASOURCE_UNAVAILABLE; uninstallcleanupscarriesruntime.package-datasources{ success: true, removed: 2 }and leaves no verdict, registration or name (control:showcase_externalstays);showcase_externalanddefaultcollisions409; a restart's rehydrate reconnects and the object reads.packages/runtime/src/declared-datasources.test.ts(new): the installed/boot split — names recorded or not, fail-fast reported or thrown, reinstall withdrawal, cleanup scoped to its package and to registrations still stamped with it, no connection service.Acceptance notes
main549203d0first (same dogfood fixture, source reverted): install200withhotLoaded: true, thenPOST/GET /data/qads_noteandGET /data/qads_secret_rowall404 OBJECT_NOT_FOUND("Object 'qads_note' is not registered"); no connection verdict for either datasource; the code-datasource set held onlydefaultandshowcase_external.connect()retains that verdict asfailed-degraded(reason naming the credential); without a bound object it isfailed-credentials. Both are the service's existing names, and the admin list maps both toerror. Recordingfailed-credentialson the fail-fast path too would be a change toservice-datasource's retained verdict on a refused boot; not made here.credentialsRef. The installed datasources now pass the round-3 gate datasource: the credential refusals send a code author (defineDatasource) to a Setup form that cannot edit a code-defined datasource; name a binding path a code author can follow #27687 measured (trigger: 'declared-auto'andCODE_DATASOURCE_NAMES_SERVICE.has(name)), so whatever it rules for code datasources reaches them.datasourceMappingrules in an installed package are not installed by this door (before or after); the helper is called with no mapped objects, so D2 (d) never fires for an installed package.artifact-kernel-factory(outside this repo) composes its own kernels; whether it connects package datasources itself is not measured here.503text for afaileddatasource (DatasourceUnavailableError,packages/objectql/src/driver-connect-errors.ts) says it "failed to connect at startup and the server was started with OS_ALLOW_DRIVER_CONNECT_FAILURE", which is false for an install-time failure (measured on this PR's dogfood read ofqads_secret_row) and, by the same branch, for a Setup-created datasource that failed.Verification
Ablations — each leg red, then restored (
git diff HEADempty)All through
scripts/ablation-replace.mjs(anchor 1 → 0, blob moved) under the verify lock. The dogfood file resolves@objectstack/cloud-connectionand@objectstack/runtimeto source (the main-side measurement above ran on reverted source with no rebuild), so no dist preflight applies.undefinedif (!installed) throw err→throw errLocal runs at head
475a5292main549203d0(dogfood fixture, source reverted, then restored):Tests 1 passed, readings in Acceptance notes.pnpm --filter @objectstack/dogfood exec vitest run test/install-local-declared-datasources.dogfood.test.ts—Tests 8 passed (8).pnpm --filter @objectstack/runtime test—Test Files 484 passed (484),Tests 7883 passed | 19 skipped (7902).pnpm --filter @objectstack/cloud-connection test—Test Files 61 passed (61),Tests 758 passed (758).pnpm --filter @objectstack/runtime typecheck,pnpm --filter @objectstack/cloud-connection typecheck— exit 0 (afterpnpm --filter @objectstack/runtime build).node scripts/pm/dispatch-gates.mjs --commands: 92 families, each run with its exit captured before any pipe; three exited 3 (prerequisite) on the first pass —check-plugin-teardown-shape --self-test(cleared by fetching the pinned commit),spec check:skill-examplesandcheck:dual-build-cjs-loads(cleared by buildingclient-react,studioand five plugin/service packages) — and exited 0 on re-run.--ran:92 derived famil(ies) accounted for — 92 run, 0 NOT-MEASURED.pnpm lint— exit 0.!in the title, no(narrowing)arm).test:repois CI's.Generated by Claude Code