Repository navigation
fix(cloud-connection,cli): the install-local ledger follows OS_HOME, so os dev --fresh starts without earlier installs - #30351
objectstack-fleet[bot] wants to merge 6 commits into
Conversation
…ting the cwd ledger once The ledger resolved from the working directory alone, so os dev --fresh and a second instance under another OS_HOME rehydrated an earlier run's installs. With OS_HOME set it now lives at <OS_HOME>/installed-packages; unset, it stays in the project. The cwd ledger is copied once into a new directory (never under a --fresh home), and os doctor reads the same resolver. Claude-Session: https://claude.ai/code/session_01Utp6CY4LWqfjLCYqpZhNCs Co-authored-by: Claude <noreply@anthropic.com>
…lay and reports its provenance Claude-Session: https://claude.ai/code/session_01Utp6CY4LWqfjLCYqpZhNCs Co-authored-by: Claude <noreply@anthropic.com>
…hout an any cast Claude-Session: https://claude.ai/code/session_01Utp6CY4LWqfjLCYqpZhNCs Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CY4LWqfjLCYqpZhNCs Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 15 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 38 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 1bba927bfdd4ff03383a1fa58a712eb59afc6900 && git checkout 1bba927bfdd4ff03383a1fa58a712eb59afc6900
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d1d90b7062af18eda616f875f9efc1ffeaac645a 2e2ebc4089991d0f1c833149f20d6385067fae2e && git checkout -B drift-repro d1d90b7062af18eda616f875f9efc1ffeaac645a && git merge --no-ff 2e2ebc4089991d0f1c833149f20d6385067fae2e
node scripts/docs-audit/affected-docs.mjs --json d1d90b7062af18eda616f875f9efc1ffeaac645a
|
|
domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC Contract reviewServed-tier: Face: declaration ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
The install-local ledger follows OS_HOME (#30217). The jobs-pull and hooks integration fixtures boot `os start --home HOME` and planted the earlier build's entry under the working directory, so boot 2 never read it (boot 1 had already created the home's ledger, so no one-time copy ran). They now write it into HOME/installed-packages; the intent of each test is unchanged. Claude-Session: https://claude.ai/code/session_01Utp6CY4LWqfjLCYqpZhNCs Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC Contract reviewServed-tier: Delta review by the lane seat, PR #30351 (card #30217), declaration face. Previous record on ① Derived judgments
② Semver levelUnchanged: ③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC 席位核对ACCEPT · head
|
Requested by Bill · project thread
domain:server#3 · os-bill · session_01Utp6CY4LWqfjLCYqpZhNCs
Fixes #30217
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
OS_HOME就放$OS_HOME/installed-packages,没设仍在CWD/.objectstack/installed-packages。旧位置的已有安装在新目录第一次出现时复制一次过去(旧文件不删),os dev --fresh不复制;os doctor读运行时同一个目录。os dev --fresh或换一个OS_HOME的第二个实例,库是新的,却把上一次装过的包又加载回来,测试互相污染。OS_HOME的os dev、项目模式的os start(OS_HOME=CWD/.objectstack)路径不变;设了别的OS_HOME的部署首次启动会复制旧账本并打一行警告。复制失败打 error,下次启动重试,旧文件始终不动。回滚即撤 PR。packages/cloud-connection/src/local-manifest-source.ts(resolveInstalledPackagesDir、adoptLegacyEntries),marketplace-install-local-plugin.ts的adoptLegacyLedger,packages/cli/src/commands/doctor.ts的readInstalledPackageEntries。pnpm --filter @objectstack/cloud-connection exec vitest run src/marketplace-install-local-storage-dir.test.ts,预期 10 个测试全过(含--fresh不复制、设OS_HOME复制一次、卸载后不复活)。What changes
@objectstack/cloud-connectionresolveInstalledPackagesDir({ env?, cwd? })is the one answer to where the ledger lives:$OS_HOME/installed-packageswhenOS_HOMEis set (a leading~expanded the way the home resolver does),CWD/.objectstack/installed-packageswhen it is not. It also returnslegacyDir: the working-directory ledger, whenever the answer moved away from it, elsenull.os devwithoutOS_HOMEkeeps its database in the project, and the ledger has to sit beside the database whose sample-data rows its entries describe;~/.objectstackwould make one ledger serve every project on the machine.os startin project mode setsOS_HOMEtoCWD/.objectstack, so its ledger is the same directory as before.LocalManifestSource()with nostorageDiruses that answer and keepslegacyDir; an explicitstorageDiradopts nothing.LocalManifestSource.adoptLegacyEntries()copies the legacy directory's.jsonfiles byte for byte into the ledger directory, once: only while the ledger directory does not exist yet. The copy goes to a sibling staging directory that is renamed into place, so a failure leaves no half-made ledger and the next boot copies again. The legacy files are left in place, so anOS_HOMEpointed at a throwaway directory never takes a project's installs with it, and they are not read again, so an uninstall in the new place sticks.list()andread()never adopt: a reader such asos doctormust not write.MarketplaceInstallLocalPlugin.rehydratecalls it before the ledger read. A copy is awarnnaming both directories and asking for the old one to be removed; a failed copy is anerror(the old ledger lists packages this runtime does not load, and the boot looks normal otherwise), naming both directories and the fix.os dev --freshnothing is adopted: the dev parent names its ephemeral home on the internal channelOS_INTERNAL_FRESH_HOME, and the resolver skips the adoption only when that value is the currentOS_HOME, so an inherited copy speaks for no other run. The channel is betweenos devand its serve child only; it is not an operator setting and is not documented as one.storageDirdoc and the adoption beforerehydrate's read change inmarketplace-install-local-plugin.ts;runInlineSeedand the install/uninstall doors are untouched.@objectstack/clios devsetsOS_INTERNAL_FRESH_HOMEon its serve child under--freshand removes an inherited copy otherwise (applyFreshHomeChannel).os doctortakes the ledger directory fromresolveInstalledPackagesDir(under its.env*overlay, asos serveloads that cascade before the ledger resolvesOS_HOME), instead of joiningcwdwithDEFAULT_INSTALLED_PACKAGES_DIR. Without this, withOS_HOMEset, doctor and the runtime would read two directories and neither would say so.OS_HOMEjoinsDOCTOR_ENV_INPUTS, so its provenance is reported. A@objectstack/cloud-connectionwithout the resolver is the existing "does not declare the ledger directory" row, now namingresolveInstalledPackagesDir.serve.tsis unchanged: its twonew MarketplaceInstallLocalPlugin(...)sites pass nostorageDir, and the default now resolves the right directory.The widening. New exports
resolveInstalledPackagesDir,INSTALLED_PACKAGES_HOME_SUBDIR,FRESH_HOME_CHANNEL_ENVand the typesInstalledPackagesLocation/LegacyLedgerAdoption; new membersLocalManifestSource.legacyDirandadoptLegacyEntries(). Nothing is removed or narrowed; nothing an author writes changes.Tests
local-manifest-source.test.ts: two new describes. The resolver's table (unset and blank, set,~, project-modeos start, the fresh channel for its own home and for another), the default constructor;adoptLegacyEntriescopies.jsonbyte for byte (an unreadable entry too, reported bylist()from its new place), leaves the legacy files, leaves no staging directory, adopts nothing once the directory exists, answersnullwithout touching the disk, and on a failed copy throws and leaves no ledger directory.marketplace-install-local-storage-dir.test.ts: a new describe booting the plugin with nostorageDir— installs land under$OS_HOME/installed-packages; a second instance under anotherOS_HOMEdoes not rehydrate the first one's install; the cwd ledger is adopted once and rehydrated, the old file kept, and an uninstall in the new place is not undone on the next boot; a fresh home adopts nothing; a fresh channel naming another home does not stop the adoption; withOS_HOMEunset the cwd ledger is read with no adoption; a failed copy logs the error naming both directories. The existing cwd-default case now pinsOS_HOMEunset.doctor-ledger-read-failure.test.ts: withOS_HOMEset doctor reports the entry under$OS_HOME/installed-packagesand nothing from a stale cwd ledger; the#6643case computes its expectation from the resolver.doctor-ledger-dir-authority.test.ts: the authority is the resolver function (not a function).dev-fresh-home-channel.test.ts(new): the CLI's spelling equals the package's; set under--fresh, removed otherwise.Acceptance notes
OS_HOMEs (neither--fresh) after this upgrade each copy the working-directory entries that existed before it. Installs made after the upgrade never leak; the old entries stop leaking once the old directory is removed, which the boot warning asks for.OS_HOMEpointed at a temporary directory (a test harness, a one-off run) would then take the project's installs into a directory that gets deleted.os doctorbefore the first boot. WithOS_HOMEset and the new directory not created yet, doctor reads the new (absent) directory and reports nothing installed; the next boot adopts the old entries. Doctor does not write, by design.Verification
Real boots of
examples/app-todothroughos dev --no-watch --log-level infowith one pre-seeded working-directory ledger entry (com.probe.ledger, standing for an earlier install), reading the plugin's boot lines and the ledger directories afterwards. Before =origin/mainsources of the five changed source files restored and both packages rebuilt; after = this branch; ablation = this branch with the fresh-channel return inresolveInstalledPackagesDirdisabled throughscripts/ablation-replace.mjs,ablation-dist-preflight.mjsconfirming the marker in 2 built files, then restored (blob equal to HEAD,git diff HEADempty,--absentover 6 built files).Before / after / ablation, three scenarios
main)OS_HOMEunsetrehydrated com.probe.ledger@1.0.0, storageCWD/.objectstack/installed-packagesos dev --freshrehydrated com.probe.ledger@1.0.0, storageCWD/.objectstack/installed-packages— the defect/tmp/objectstack-dev-…/installed-packages, cwd ledger untouchedcopied 1 installed package(s) … into /tmp/objectstack-dev-…/installed-packages,rehydrated com.probe.ledger@1.0.0— the defect is backOS_HOME=/tmp/os30217-home-…rehydrated, storageCWD/…, nothing under the homecopied 1 installed package(s) from CWD/… into HOME_DIR/installed-packages,rehydrated, storageHOME_DIR/installed-packages; cwd file keptTests and gates, on head
0947c7af(after mergingorigin/mainatc1240a38; the closure rebuilt after the merge). The final head299ca6ddchanges only the changeset's wording (path spellings without angle brackets); the nine changeset- and text-reading families were re-run there, all exit 0:check-adr-0087-registration,check-changeset-no-major,check-empty-changeset,check:changeset-gate-self-tests,check:doc-authoring,check:issue-citations(both forms),check:nul-bytes,check-closing-keyword-parity.pnpm --filter @objectstack/cloud-connection test: Test Files 70 passed (70), Tests 863 passed (863);typecheck(both programs) exit 0.marketplace-install-local-storage-dir.test.tsre-run after the last commit: 10/10.pnpm --filter @objectstack/cli exec vitest run --project unit: 382 of 385 files passed on the first head; the twopublished-subpath-*pins failed only becausepackages/cliwas not built yet (their own preflight says so) and pass after the build, anddoctor-env-provenancenamedOS_HOMEas an undeclared doctor input, which the second commit declares. Re-run after the fixes: everydoctor*file, 14 files / 212 tests, plusdev-fresh-home-channeland bothpublished-subpathpins, all passed;pnpm --filter @objectstack/cli typecheckexit 0. Theintegrationtier is CI's.eslint --no-inline-configover the ten changed source and test files: 0 problems.node scripts/pm/dispatch-gates.mjs --commandsderives 63 commands; each was run with its exit code captured before any pipe, and--ranreconciles 63 derived, 61 run, 2 NOT MEASURED, 0 unrun.The 63 derived commands and their exit codes
NOT MEASURED, both prerequisite refusals (exit 3), left to CI's
Lint & Repo Gates:check:dual-build-cjs-loads(packages outside this card's closure have nodist/in this worktree, e.g.@objectstack/studio,@objectstack/client-react) andcheck:i18n-coverage(examples/app-showcase's@objectstack/connector-slackhas no build output here).Dogfood: n/a — no
!in the title and no(narrowing)arm.🤖 Generated with Claude Code
https://claude.ai/code/session_01Utp6CY4LWqfjLCYqpZhNCs
Generated by Claude Code