Skip to content

fix(cloud-connection): register the per-org seed replayer on marketplace install-local - #9341

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-9070-register-seed-replayer-install-local
Aug 17, 2026
Merged

fix(cloud-connection): register the per-org seed replayer on marketplace install-local#9341
os-zhuang merged 3 commits into
mainfrom
claude/issue-9070-register-seed-replayer-install-local

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Fixes#9070

The defect

MarketplaceInstallLocalPlugin.applySideEffects merged an installed package's data blocks onto the kernel's shared seed-datasets service, but never registered the seed-replayer service that consumes them.

registerSeedReplayerOnce lives in AppPlugin's seeder path, so it runs only when the host runtime declares seed data of its own. A runtime that declares none — objects: [], no data, which is exactly the shape a marketplace install targets and exactly what apps/objectos-ee is — therefore ended up with seed-datasets populated and seed-replayer absent. On a walled (isolated / group) deployment the org-scoping middleware then read both, found the datasets, found no replayer, logged

WARN [org-scoping] per-org seed: datasets present but no replayer registered

and did nothing. Every organization founded after the install received zero rows of the app that was just installed.

The reason this survived is the asymmetry in how it presents: the installer's own organization looks perfectly correct, because it was seeded inline at install time rather than by the replay. The obvious probe reports success.

The fix

applySideEffects step 2 now registers the replayer next to the merge, through the runtime's registerSeedReplayerOnce helper. Placing it beside the merge rather than in the immediate-seed branch is deliberate on two counts: the merge and its consumer land together so the "merge landed, consumer never did" split cannot recur, and step 2 also runs on the rehydrate path (seedNow: false), which is where a restarted runtime gets its replayer back.

Register-once is the whole safety argument, and it belongs to the helper rather than a caller-side probe: a host that already has a replayer keeps it, and the incumbent re-reads the same shared list, so it replays this package's datasets too. Nothing is displaced, nothing duplicated.

The lazy import('@objectstack/runtime') plus typeof guard plus inline fallback mirrors the sibling mergeSeedDatasetsIntoKernel exactly. The fallback is not decoration here: five sibling suites in this package mock @objectstack/runtime with only SeedLoaderService and recordSeedOutcome, so a helper-only implementation would silently no-op under them, and an older runtime build would silently keep the defect.

Two smaller points that fall out of the consumer contract:

  • The replayer reads the live shared list on every call, never a snapshot from when the closure was built — an organization founded after a later install must still replay that install's seeds.
  • runInlineSeed now also returns the loader's raw error array (errorList) beside the existing count. The middleware reports summary.errors.length and samples summary.errors.slice(0, 5); handing it a number makes both read as "0 error(s)" through its optional chaining, i.e. a failed replay that reports clean. Existing callers keep using the count.

The replayer's two service lookups carry their declared contract types (IObjectQLEngine, IMetadataService from @objectstack/spec/contracts) rather than any, matching the typed-lookup precedent already in this file. A split declaration with an untyped let erases the slot exactly as const ql: any = … does; this file is grandfathered for its existing sites only, and it stays at its baseline of 16.

Verification

New pin: packages/cloud-connection/src/marketplace-install-local-seed-replayer.test.ts, 9 cases, all in the multi-tenant / no-host-data direction. The context is a standard PluginContext with no .kernel handle over a kernel-faithful registry, where getService throws on a miss and registerService throws on a duplicate — both throws are the framework traps #3453 was about, and a permissive fake would let a broken registration pass. Nothing mounts an AppPlugin, on purpose: the absence of a host replayer is the scenario.

Reverse-verified with the direction predicted before the run. Against the unfixed plugin at ceb37ba with the new test file kept: 8 failed, 1 passed — three clean expected 'undefined' to be 'function' assertions, four replayer is not a function TypeErrors, one spy-never-called. The single green is never displaces a replayer the host already registered, which is a no-op-by-construction control rather than a defect pin: unfixed code registers nothing, so the incumbent trivially survives. After the fix, 9/9 green.

Verified at head 3d4eb45ce, reported by CI job:

  • ESLint job — the whole family it carries, not just the linter: pnpm lint, check:slot-lookup, check:query-options-erasure, check:verify-stand-in, check:nul-bytes, check:stack-collection-maps, check:pm-skill-ratchet, check:pm-skill-id-lint, check:pm-dispatch-gates, check:partof-closing-keyword, check:pm-half-states, check:doc-authoring, plus check:engine-double-contract and check:where-matcher. All exit 0. check:slot-lookup measures this file at 16 against a baseline of 16 — the gate errors in both directions, so green here is only reachable at exactly the ceiling. The baseline file is untouched.
  • TypeScript Type Check jobcheck:type-check-coverage green; check:type-check-debt --re-measure was run on a fully built workspace closure (70 turbo tasks) rather than skipped, since it refuses outright on an unbuilt tree and that refusal means not measured: 33 ledger entries re-measured, none above its recorded number, surplus: none. Direct tsc --noEmit on packages/cloud-connection reports 13, exactly its frozen DEBT, with zero in either file this PR touches — the contract types surfaced no error the any had been hiding.
  • docs-drift-check jobdocs-audit/check-affected-docs green.
  • changeset gatescheck-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check:changeset-gate-self-tests, check:objectui-changeset all green.
  • Testspackages/cloud-connection: 22 files, 173 tests, all passing; the new pin alone 9/9.

Expected cross-repo consequence

apps/objectos-ee/test/hotcrm-install-local.acceptance.ts in objectstack-ai/cloud pins the defect — the datasets present but no replayer registered line is its tripwire — and is expected to go RED when this lands. It should be promoted into a full per-organization assertion, not repaired. That repo is untouched here; the follow-up card there is the accepting seat's obligation.

#3453 landed the merge half that this builds on.


Generated by Claude Code

…ace install-local (#9070)
`applySideEffects` merged an installed package's seed datasets onto the shared
`seed-datasets` service but never registered the `seed-replayer` that consumes
them. The replayer lives in AppPlugin's seeder path, so a host runtime that
declares no seed data of its own — the shape a marketplace install targets —
ended up with datasets present and no replayer, and every organization founded
after the install booted empty on a walled deployment.
Register via the runtime's register-once helper next to the merge, on both the
install and the rehydrate path, with the same lazy-import guard and inline
fallback the sibling merge already uses.
Co-Authored-By: Claude <noreply@anthropic.com>
…ehydrate pin
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cloud-connection, touching 8 documentable anchor(s).

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

  • content/docs/deployment/cli.mdx(via MarketplaceInstallLocalPlugin (symbol))
What this run could not see

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 origin/mainpackageMentionDocs.

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 origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 17, 2026
…ayer's lookups
The two new lookups in `buildSeedReplayer` used a split declaration and an
untyped `let`, which erases the slot's contract exactly as `const ql: any`
does — the fourth erasure shape named in eslint.config.mjs. That pushed this
file's slot-lookup count 16 -> 18 and failed the ratchet in the ESLint job.
Type both against their declared contracts, matching the typed-lookup precedent
already in this file: `IObjectQLEngine` for `objectql` and `IMetadataService`
for `metadata`, both from @objectstack/spec/contracts. The baseline is
untouched and the file measures 16 again.
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32043566755 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core — 失败步骤: Set up job(日志不可读,点进 job 看)

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 3 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks Aug 17, 2026
@claude

claudeBot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Queue-eviction triage — same infrastructure incident, no action on this PR

PM seat, session session_012WKSnqAaoqtW3QX7SSf1Vk.

Queue build 32043566755: the only failure is Test Core, failing at step Set up job — identical to the eviction of #9347 twelve minutes earlier:

Download action repository 'actions/download-artifact@v8'
##[warning]Failed to download action … 429 (Too Many Requests) ×3
##[error]Failed to download archive … after 3 attempts.

No test executed, so none of the workflow's three triage branches apply — this is a setup-phase infrastructure failure, and it carries no information about this diff.

Scope, measured rather than assumed.codeload.github.com has been refusing GitHub Actions' own action downloads (429/503) repo-wide for ~50 minutes: actions/download-artifact (×4 now), actions/labeler, CodelyTV/pr-size-labeler, lycheeverse/lychee-action. It is not confined to this lane — unrelated branches show it, and GitHub's REST API returned 502/503 in the same window.

Action: ⛔ not re-queuing, and deliberately not retrying further

Both of this seat's ACCEPTed PRs (#9341, #9347) are now evicted by the same cause. A re-queue during an active window would very likely be evicted again, and each entry forces a rebuild for every PR behind it — the exact cost the triage workflow warns about. One earlier spaced re-run did clear the non-required jobs on this PR once a window passed, so retries work; timing is the whole question.

⇒ Holding both PRs, watching for evidence that action downloads have recovered, then re-queuing them. All five required checks are green on 3d4eb45ce; nothing is being pushed to this branch.

The situation has been reported to the maintainer, since it is consuming queue capacity across every lane rather than being a defect in any one PR.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-zhuang@claude