Skip to content

fix(cloud-connection,cli): the install-local ledger follows OS_HOME, so os dev --fresh starts without earlier installs - #30351

Queued
objectstack-fleet[bot] wants to merge 6 commits into
mainfrom
claude/project-thread-14pct9
Queued

objectstack-fleet[bot] wants to merge 6 commits into
mainfrom
claude/project-thread-14pct9

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

domain:server#3 · os-bill · session_01Utp6CY4LWqfjLCYqpZhNCs

Fixes #30217

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么: 本地安装账本(install-local 缓存目录)不再固定在工作目录:设了 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-connection

  • resolveInstalledPackagesDir({ env?, cwd? }) is the one answer to where the ledger lives: $OS_HOME/installed-packages when OS_HOME is set (a leading ~ expanded the way the home resolver does), CWD/.objectstack/installed-packages when it is not. It also returns legacyDir: the working-directory ledger, whenever the answer moved away from it, else null.
  • Unset stays in the project on purpose. os dev without OS_HOME keeps its database in the project, and the ledger has to sit beside the database whose sample-data rows its entries describe; ~/.objectstack would make one ledger serve every project on the machine. os start in project mode sets OS_HOME to CWD/.objectstack, so its ledger is the same directory as before.
  • LocalManifestSource() with no storageDir uses that answer and keeps legacyDir; an explicit storageDir adopts nothing.
  • LocalManifestSource.adoptLegacyEntries() copies the legacy directory's .json files 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 an OS_HOME pointed 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() and read() never adopt: a reader such as os doctor must not write.
  • MarketplaceInstallLocalPlugin.rehydrate calls it before the ledger read. A copy is a warn naming both directories and asking for the old one to be removed; a failed copy is an error (the old ledger lists packages this runtime does not load, and the boot looks normal otherwise), naming both directories and the fix.
  • Under os dev --fresh nothing is adopted: the dev parent names its ephemeral home on the internal channel OS_INTERNAL_FRESH_HOME, and the resolver skips the adoption only when that value is the current OS_HOME, so an inherited copy speaks for no other run. The channel is between os dev and its serve child only; it is not an operator setting and is not documented as one.
  • Only the constructor/storageDir doc and the adoption before rehydrate's read change in marketplace-install-local-plugin.ts; runInlineSeed and the install/uninstall doors are untouched.

@objectstack/cli

  • os dev sets OS_INTERNAL_FRESH_HOME on its serve child under --fresh and removes an inherited copy otherwise (applyFreshHomeChannel).
  • os doctor takes the ledger directory from resolveInstalledPackagesDir (under its .env* overlay, as os serve loads that cascade before the ledger resolves OS_HOME), instead of joining cwd with DEFAULT_INSTALLED_PACKAGES_DIR. Without this, with OS_HOME set, doctor and the runtime would read two directories and neither would say so. OS_HOME joins DOCTOR_ENV_INPUTS, so its provenance is reported. A @objectstack/cloud-connection without the resolver is the existing "does not declare the ledger directory" row, now naming resolveInstalledPackagesDir.
  • serve.ts is unchanged: its two new MarketplaceInstallLocalPlugin(...) sites pass no storageDir, and the default now resolves the right directory.

The widening. New exports resolveInstalledPackagesDir, INSTALLED_PACKAGES_HOME_SUBDIR, FRESH_HOME_CHANNEL_ENV and the types InstalledPackagesLocation / LegacyLedgerAdoption; new members LocalManifestSource.legacyDir and adoptLegacyEntries(). 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-mode os start, the fresh channel for its own home and for another), the default constructor; adoptLegacyEntries copies .json byte for byte (an unreadable entry too, reported by list() from its new place), leaves the legacy files, leaves no staging directory, adopts nothing once the directory exists, answers null without 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 no storageDir — installs land under $OS_HOME/installed-packages; a second instance under another OS_HOME does 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; with OS_HOME unset the cwd ledger is read with no adoption; a failed copy logs the error naming both directories. The existing cwd-default case now pins OS_HOME unset.
  • doctor-ledger-read-failure.test.ts: with OS_HOME set doctor reports the entry under $OS_HOME/installed-packages and nothing from a stale cwd ledger; the #6643 case 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

  • A brand-new non-fresh home adopts the pre-upgrade ledger once. Two instances started under two new 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.
  • Copy, not move. A move would converge sooner, but any OS_HOME pointed 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 doctor before the first boot. With OS_HOME set 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-todo through os dev --no-watch --log-level info with 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/main sources of the five changed source files restored and both packages rebuilt; after = this branch; ablation = this branch with the fresh-channel return in resolveInstalledPackagesDir disabled through scripts/ablation-replace.mjs, ablation-dist-preflight.mjs confirming the marker in 2 built files, then restored (blob equal to HEAD, git diff HEAD empty, --absent over 6 built files).

Before / after / ablation, three scenarios
Scenario Before (main) After (this PR) Ablation (fresh guard off)
OS_HOME unset rehydrated com.probe.ledger@1.0.0, storage CWD/.objectstack/installed-packages same same
os dev --fresh rehydrated com.probe.ledger@1.0.0, storage CWD/.objectstack/installed-packages — the defect no rehydrate, storage /tmp/objectstack-dev-…/installed-packages, cwd ledger untouched copied 1 installed package(s) … into /tmp/objectstack-dev-…/installed-packages, rehydrated com.probe.ledger@1.0.0 — the defect is back
OS_HOME=/tmp/os30217-home-… rehydrated, storage CWD/…, nothing under the home copied 1 installed package(s) from CWD/… into HOME_DIR/installed-packages, rehydrated, storage HOME_DIR/installed-packages; cwd file kept same as after

Tests and gates, on head 0947c7af (after merging origin/main at c1240a38; the closure rebuilt after the merge). The final head 299ca6dd changes 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.ts re-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 two published-subpath-* pins failed only because packages/cli was not built yet (their own preflight says so) and pass after the build, and doctor-env-provenance named OS_HOME as an undeclared doctor input, which the second commit declares. Re-run after the fixes: every doctor* file, 14 files / 212 tests, plus dev-fresh-home-channel and both published-subpath pins, all passed; pnpm --filter @objectstack/cli typecheck exit 0. The integration tier is CI's.
  • eslint --no-inline-config over the ten changed source and test files: 0 problems.
  • node scripts/pm/dispatch-gates.mjs --commands derives 63 commands; each was run with its exit code captured before any pipe, and --ran reconciles 63 derived, 61 run, 2 NOT MEASURED, 0 unrun.
The 63 derived commands and their exit codes
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 3
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n :: exit 0
pnpm check:i18n-coverage :: exit 3
pnpm check:i18n-walk-parity :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:route-envelope :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

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 no dist/ in this worktree, e.g. @objectstack/studio, @objectstack/client-react) and check:i18n-coverage (examples/app-showcase's @objectstack/connector-slack has 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

…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>
@github-actions github-actions Bot added the size/l label Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/cloud-connection, touching 24 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/cloud-connection/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/automation/flows.mdx (via os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/automation/jobs.mdx (via os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/data-modeling/drivers.mdx (via OS_MULTI_ORG_ENABLED (literal, a string literal in DOCTOR_ENV_INPUTS), OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))
  • content/docs/data-modeling/indexing.mdx (via os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/deployment/backup-restore.mdx (via OS_HOME (literal, a string literal in DOCTOR_ENV_INPUTS))
  • content/docs/deployment/cli.mdx (via MarketplaceInstallLocalPlugin (symbol, a top-level class), OS_HOME (literal, a string literal in DOCTOR_ENV_INPUTS), OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS), os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/deployment/environment-variables.mdx (via OS_HOME (literal, a string literal in DOCTOR_ENV_INPUTS), OS_MULTI_ORG_ENABLED (literal, a string literal in DOCTOR_ENV_INPUTS), OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS), os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/deployment/production-readiness.mdx (via os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/deployment/tenancy-modes.mdx (via OS_MULTI_ORG_ENABLED (literal, a string literal in DOCTOR_ENV_INPUTS), OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))
  • content/docs/plugins/packages.mdx (via OS_MULTI_ORG_ENABLED (literal, a string literal in DOCTOR_ENV_INPUTS), OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))
  • content/docs/protocol/backward-compatibility.mdx (via os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/protocol/kernel/config-resolution.mdx (via os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/protocol/kernel/http-protocol.mdx (via os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/protocol/objectql/schema.mdx (via OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))
  • content/docs/upgrading.mdx (via OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))

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

  • content/docs/releases/v16.mdx (via os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/releases/v17/17-1.mdx (via OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))
  • content/docs/releases/v17/17-3.mdx (via OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))
  • content/docs/releases/v17/17-4.mdx (via OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))
  • content/docs/releases/v17/17-5.mdx (via os doctor (command, read off packages/cli/src/commands/doctor.ts))
  • content/docs/releases/v17/17-7.mdx (via OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))
  • content/docs/releases/v18/index.mdx (via OS_MULTI_ORG_ENABLED (literal, a string literal in DOCTOR_ENV_INPUTS), OS_TENANCY_POSTURE (literal, a string literal in DOCTOR_ENV_INPUTS))

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
  • 1 changed file(s) yielded no anchor (packages/cloud-connection/src/index.ts) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: os dev (command, 43 pages)
  • 6 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 38 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 d1d90b7062af18eda616f875f9efc1ffeaac645a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1bba927bfdd4ff03383a1fa58a712eb59afc6900 — the merge of head 2e2ebc4089991d0f1c833149f20d6385067fae2e into base d1d90b7062af18eda616f875f9efc1ffeaac645a, 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 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

⚠️ 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 d1d90b7062af18eda616f875f9efc1ffeaac645a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 299ca6ddb2a8748be8191820b22fd21f306b31e2
Local-runs: none
Scope: packages/cloud-connection/src/local-manifest-source.ts, packages/cloud-connection/src/index.ts, packages/cloud-connection/src/marketplace-install-local-plugin.ts, packages/cli/src/commands/doctor.ts, packages/cli/src/commands/dev.ts, .changeset/30217-install-ledger-follows-os-home.md

Face: declaration (Clause-②: yes (widening) line-initial on the PR body and the changeset). No published schema, no governed path, no narrowing in the committed tree, so the lane seat records this review at TIER_DEFAULT. Reads: the full diff, card #30217 (claim 6650344744, amendment 6650402032, os-dev report 6652012535), the PR body.

① Derived judgments

  1. Public surface widened. @objectstack/cloud-connection's index newly exports INSTALLED_PACKAGES_HOME_SUBDIR, FRESH_HOME_CHANNEL_ENV, resolveInstalledPackagesDir and the types InstalledPackagesLocation, LegacyLedgerAdoption; LocalManifestSource gains readonly legacyDir and adoptLegacyEntries(). DEFAULT_INSTALLED_PACKAGES_DIR stays exported with the same value. No signature narrows (the constructor still takes an optional storageDir), so yes (widening) is the right declaration.
  2. Where the ledger lives. resolveInstalledPackagesDir: OS_HOME unset → CWD/.objectstack/installed-packages (unchanged); set → OS_HOME/installed-packages; os start project mode (OS_HOME = CWD/.objectstack) resolves to the old directory and adopts nothing (dir === legacy). An explicit storageDir is used as given and adopts nothing.
  3. Adoption. Copy-only, once (only while the new directory is absent), staged in a sibling mkdtemp and renamed into place, staging removed on failure; old files never deleted. A failed copy is an error log naming both directories, not a boot refusal; a done copy is a warn. Readers (list, read, os doctor) never write.
  4. --fresh. os dev sets OS_INTERNAL_FRESH_HOME on the serve child only under --fresh and deletes an inherited copy otherwise; the resolver honours it only when it names the same home, so a stale inherited value cannot suppress adoption elsewhere. The two spellings (cli constant, cloud-connection constant) are held equal by dev-fresh-home-channel.test.ts.
  5. os doctor. Reads resolveInstalledPackagesDir (under the .env* overlay, with OS_HOME added to DOCTOR_ENV_INPUTS) instead of joining cwd with the string, so doctor and the runtime read one directory; a resolver that throws is a readerFailure row, a missing function the existing dirAuthorityMissing row.

② Semver level

@objectstack/cloud-connection and @objectstack/cli patch: AGENTS.md step 3, a non-protocol change (widening) ships patch; Metadata-protocol: none. Nothing removed or refused, so no BREAKING banner and no ADR-0087 disposition are owed. The changeset's "Existing installs" bullet states the move and the one-time copy an operator sees.

③ Boundary flags

Implemented-by: claude/project-thread-14pct9
Reviewed-by: session_01Utp6CbPxYq1wQmUNWvMZkC

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>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 2e2ebc4089991d0f1c833149f20d6385067fae2e
Local-runs: none
Scope: packages/cli/test/package-install-local-jobs-pull.integration.test.ts, packages/cli/test/package-install-local-hooks.integration.test.ts

Delta review by the lane seat, PR #30351 (card #30217), declaration face. Previous record on 299ca6ddb2a8748be8191820b22fd21f306b31e2 (PR comment 6652045064): PASS. That head then went red on Test Core (4/8) (job 127104154080, package-install-local-jobs-pull.integration.test.ts:464/:476) and Test Core (1/8) (job 127104154082, package-install-local-hooks.integration.test.ts:495/:502). This record judges the one commit 299ca6dd → 2e2ebc40 (test(cli): write the earlier build's ledger entry into the home's ledger); every item of the earlier record the delta does not touch stands as written there.

① Derived judgments

  • Cause of the red, owned by this PR. Both fixtures boot twice with --home <runtimeDir>/home and, between boots, planted the "earlier build" entry in join(runtimeDir, '.objectstack', 'installed-packages'). Under this PR the ledger is OS_HOME/installed-packages; boot 1's installs already created it, so the one-time adoption (new directory absent) does not run and boot 2 never read the planted entry: the rehydrated object answered 404, the bindable pull job was not scheduled, and no withheld-item warn printed. That is the behaviour the earlier record describes (judgment 3), so the fixtures, not the source, were stale.
  • The fix. Each fixture now plants the entry in join(home, 'installed-packages'), the directory the runtime reads, with a two-line note citing install-local: the cached-manifest directory is resolved from the working directory, not OS_HOME, so os dev --fresh rehydrates a previous install #30217. Test intent (an entry an earlier build wrote is rehydrated; its unbindable item is withheld by name) unchanged; assertions unchanged; no source file touched.
  • No third site. The dev's repo-wide grep finds the cwd-ledger join only in the doctor unit tests (posture-independence, dir-authority, read-failure), which pass no --home and set no OS_HOME, so the cwd ledger is the right reading there; read-failure's OS_HOME case already reads the home ledger.

② Semver level

Unchanged: @objectstack/cloud-connection and @objectstack/cli patch; test-only delta.

③ Boundary flags

  • Adoption-from-cwd stays covered by the cloud-connection unit tests from the earlier head; these two integration files now cover rehydrate under a home ledger, which is what they always meant to.
  • CI on 2e2ebc40 still running at this reading; landing waits for green.

Implemented-by: claude/project-thread-14pct9
Reviewed-by: session_01Utp6CbPxYq1wQmUNWvMZkC

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

席位核对

ACCEPT · head 2e2ebc4089991d0f1c833149f20d6385067fae2e · card #30217 (claim 6650344744, amendment 6650402032) · 2026-11-05T07:54Z

  • Shape: draft PR to main, author objectstack-fleet[bot] (relay), attribution block then Fixes #30217. 13 files, +651 −72. check-governed-merges.mjs --pr 30351: NOT governed, 723 changed lines ≤ 3000 ⇒ ordinary queue landing.
  • Declarations: Clause-②: yes (widening), Metadata-protocol: none; changesets @objectstack/cloud-connection patch and @objectstack/cli patch (AGENTS.md step 3: a non-protocol widening ships patch). No BREAKING banner owed.
  • Review of record: seat TIER_DEFAULT record PASS on 299ca6dd (6652045064) and delta record PASS on 2e2ebc40 (6652363537). The delta is the two integration fixtures that planted the "earlier build" entry in the cwd ledger under --home; they now plant it in the home's ledger the runtime reads. Test-only, source unchanged.
  • Gates (check-runs on this head): 48 runs, 35 success, 13 skipped, 0 failed; Test Core ×8 + aggregate (red on 299ca6dd, jobs 127104154080 / 127104154082) now green. Skips read by name (path-gated, opt-in harnesses): check-expected-skips.mjs cannot run in this container (yaml not installed).
  • Accepted deviation: two commits after the first report (changeset path spelling; the fixture rework), no force-push.
  • Landing: ready + auto-merge by the seat.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 07:55
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 07:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Any commits made after this event will not be merged.

This branch has not been deployed

No deployments
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

install-local: the cached-manifest directory is resolved from the working directory, not OS_HOME, so os dev --fresh rehydrates a previous install

1 participant