Repository navigation
fix(cloud-connection): install-local judges datasource-name holders with one judge that reads the engine and fails closed, and a rehydrate never takes over a held name - #28418
Conversation
Claude-Session: https://claude.ai/code/session_01QUs3BuoLdzNi2KSkEgUS5q Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUs3BuoLdzNi2KSkEgUS5q Co-authored-by: Claude <noreply@anthropic.com>
…der, fails closed Claude-Session: https://claude.ai/code/session_01QUs3BuoLdzNi2KSkEgUS5q Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUs3BuoLdzNi2KSkEgUS5q Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
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 8b59bdef9aa40458b8b950c1a4b2f6e08d3bde0e && git checkout 8b59bdef9aa40458b8b950c1a4b2f6e08d3bde0e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a727ba26b17280bc5f3cdd9a9d6addbc5356389a 039a82db8075949ddf17955460b14673445d3366 && git checkout -B drift-repro a727ba26b17280bc5f3cdd9a9d6addbc5356389a && git merge --no-ff 039a82db8075949ddf17955460b14673445d3366
node scripts/docs-audit/affected-docs.mjs --json a727ba26b17280bc5f3cdd9a9d6addbc5356389a
|
…stall-local 503) Claude-Session: https://claude.ai/code/session_01QUs3BuoLdzNi2KSkEgUS5q Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#4 · os-zhuang · session_01QUs3BtyH9zgxKzYrJiH18J Contract reviewServed-tier: Face: the declaration face alone ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:server#4 · os-zhuang · session_01QUs3BtyH9zgxKzYrJiH18J ACCEPT · PR #28418 · Head-sha 维护者速读
席位意见:一个判定服务安装和重启两条路,读失败按"未判定"拒绝而不是放行,卸载不再关别人的池;可合。 席位核对
|
Requested by Sam · project thread
Fixes #28143
Clause-②: no
Metadata-protocol: none
维护者速读
datasourceNameHolder来做;它除了读元数据,还读引擎上的驱动和代码数据源集合,读不到时拒绝安装(503),不再放行。重启时的 rehydrate 遇到被别人占用的名字,不再接管,只记一条 error 日志;卸载只关闭包自己打开的连接池。packages/runtime/src/declared-datasources.ts(datasourceNameHolder、leaveHeldNames、withdrawPackageDatasources的found判断),packages/cloud-connection/src/marketplace-install-local-plugin.ts2a 段与findHeldDatasourceName。pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/declared-datasources.test.ts,预期 17 个用例全绿。What changes
@objectstack/runtimeexportsdatasourceNameHolder(ctx, appId, name)→free | held (holder) | unreadable (reading, cause). It reads, in order: the host'sdefault; the metadata registry (an item not stamped with the package: a Setup row, another package's); the code-datasource set (a name another installed package recorded, or one the host registers from code); the data engine (objectql.getDriverByName, a driver with no metadata item: the legacyonEnablebridge). The package's own earlier registration or recorded pool isfree, so a reinstall passes. An absent service holds nothing; a read that throws isunreadable, neverfree.marketplace-install-local-plugin.ts) judges with it. A held name is the existing409 RESOURCE_CONFLICT(details.datasource), now including an engine-only holder. A holder that cannot be read is503 SERVICE_UNAVAILABLEwithdetails: { datasource, reading }, before any write, worded like the access-upgrade check's 503 ("a check that did not run is never taken for no holder"). A runtime without the judge is that 503 as well.connectPackageDatasources(install and every rehydrate) judge each declared name after withdrawing the package's own earlier registrations. A held or unreadable name is left out of all three steps (no code registration, not recorded in the code-datasource set, not connected), and oneerrorline names each, its holder, the consequence and the remedy. The boot path (installedunset) is unchanged and still handsconnectDeclaredthe very list it was given.already-registered; the withdrawal does notdisconnectthose (their holder's pool), while still removing the package's registration and name.Why
The three edges the card names, each measured on
origin/main75ecaf4abefore the fix, with the tests below run against the unchanged source:metadata.getthrowing → the install answered200(expected 200 to be 503).200(expected 200 to be 409).pkg_store, an installed call returnedpkg_store's verdict alongside the package's other datasource (it registered, recorded and "connected" the name); and an uninstall disconnected a pool the connect had only found (disconnectedwas['pkg_store','pkg_vault'], expected['pkg_vault']).The card's choice for (1)+(2) was "fail closed or read the engine": both are done, since the engine read alone does not close (1).
Tests
packages/runtime/src/declared-datasources.test.ts: 9 new cases. The judge: each holder kind (default, Setup row, another package, engine-only), a code-set name, a metadata or engine read fault isunreadable, the package's own registration and recorded pool arefree(with a control from another package's side), nothing anywhere isfree. The installed call: a Setup row is not taken over and the uninstall leaves its pool and item alone; an engine-only holder is not taken over; an unreadable holder is not taken over and is reported; a pool foundalready-registeredis not disconnected by the uninstall. The fake kernel gained an opt-in engine whose driver registry the fake connect fills, as the real one does.packages/cloud-connection/src/marketplace-install-local-datasource-holder.test.ts(new): through the real install route, a metadata read fault answers503 SERVICE_UNAVAILABLEand writes no ledger entry or registration; an engine-only holder answers409 RESOURCE_CONFLICT; a control with an unrelated engine driver installs200.Acceptance notes
errorline. Nothing new is added to the GET listing or the ledger file; a listing marker likenotLoadedwould be a separate wire decision.metadataservice is not an outage: no metadata holder can exist there, and the package's own registration is skipped the same way, so the judge goes on to the code set and the engine rather than refusing.packages/runtime/src/index.ts, the barrel line exportingdatasourceNameHolderand its type.Verification
All readings are on head
6aa099f6af(baseorigin/main75ecaf4a; the 5 commits main gained since touch none of these files), in a dedicated worktree.Tests 2 failed | 1 passed (3)(expected 200 to be 503,expected 200 to be 409, control green); runtimeTests 9 failed | 8 passed (17)(the 5 judge cases have no export to call; the 4 installed-call cases fail on the takeover and the eviction).pnpm --filter @objectstack/runtime build && test && typecheckunder the verify lock →VERDICT command-exit 0;Test Files 492 passed (492),Tests 8010 passed | 19 skipped (8029).pnpm --filter @objectstack/cloud-connection test && typecheckunder the lock →VERDICT command-exit 0;Test Files 63 passed (63),Tests 768 passed (768).install-local-declared-datasources.dogfood.test.tsafter a fullpnpm build→Tests 8 passed (8)(refusals ofshowcase_externalanddefault, install, uninstall, rehydrate all unchanged).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 60 commands; all 60 exited 0. Three first answered3/ timed out for a missing prerequisite (teardown-shape self-test on a shallow clone;check:dual-build-cjs-loadswithout a full build;check:type-check-debtcut by my own 300 s cap during its closure build) and were re-run to exit 0 aftergit fetch --unshallowand a fullpnpm build.--ran:60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN.pnpm lint→ exit 0.check:platform-checklistnot run: nothing exported is renamed or removed (one export added).!in the title, no(narrowing)arm on the claim'sClause-②:line).Ablations (one-off, not kept)
Each through
node scripts/ablation-replace.mjsunder the verify lock; the unit suites import the mutated module from source, so no rebuild was involved. Predictions written before each run.declared-datasources.ts, anchorif (holding.status === 'free') continue;→… || holding) continue;(nothing is ever left out). Predicted the three not-taken-over cases red.anchor 1 -> 0, blob ddb9ddcbc7ef -> 6f7e7e257f17;Tests 3 failed | 14 passed (17), exactly those three; restoredblob == HEAD,git diff HEADempty.declared-datasources.ts, the withdrawal's&& !record?.found.has(name)dropped (every recorded pool closed). Predicted the found-pool case red.anchor 1 -> 0, blob ddb9ddcbc7ef -> 334b4cea3bdb;Tests 1 failed | 16 passed (17); restoredblob == HEAD.marketplace-install-local-plugin.ts, the 503 branch made unreachable (a guard that is always false: the current time is negative). Predicted the read-fault case red.anchor 1 -> 0, blob 86f1411152ed -> 32eb6f3d33e1;Tests 1 failed | 2 passed (3); restoredblob == HEAD.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: n/a, not touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code