Uh oh!
There was an error while loading. Please reload this page.
fix(cloud-connection): register the per-org seed replayer on marketplace install-local - #9341
Conversation
…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>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not seeCoarse fallback — 3 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop):
|
…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>
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32043566755 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
Queue-eviction triage — same infrastructure incident, no action on this PRPM seat, session Queue build 32043566755: the only failure is 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. Action: ⛔ not re-queuing, and deliberately not retrying furtherBoth 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 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 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9070
The defect
MarketplaceInstallLocalPlugin.applySideEffectsmerged an installed package'sdatablocks onto the kernel's sharedseed-datasetsservice, but never registered theseed-replayerservice that consumes them.registerSeedReplayerOncelives inAppPlugin's seeder path, so it runs only when the host runtime declares seed data of its own. A runtime that declares none —objects: [], nodata, which is exactly the shape a marketplace install targets and exactly whatapps/objectos-eeis — therefore ended up withseed-datasetspopulated andseed-replayerabsent. On a walled (isolated/group) deployment the org-scoping middleware then read both, found the datasets, found no replayer, loggedand 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
applySideEffectsstep 2 now registers the replayer next to the merge, through the runtime'sregisterSeedReplayerOncehelper. 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')plustypeofguard plus inline fallback mirrors the siblingmergeSeedDatasetsIntoKernelexactly. The fallback is not decoration here: five sibling suites in this package mock@objectstack/runtimewith onlySeedLoaderServiceandrecordSeedOutcome, 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:
runInlineSeednow also returns the loader's raw error array (errorList) beside the existing count. The middleware reportssummary.errors.lengthand samplessummary.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,IMetadataServicefrom@objectstack/spec/contracts) rather thanany, matching the typed-lookup precedent already in this file. A split declaration with an untypedleterases the slot exactly asconst 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 standardPluginContextwith no.kernelhandle over a kernel-faithful registry, wheregetServicethrows on a miss andregisterServicethrows 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
ceb37bawith the new test file kept: 8 failed, 1 passed — three cleanexpected 'undefined' to be 'function'assertions, fourreplayer is not a functionTypeErrors, one spy-never-called. The single green isnever 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: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, pluscheck:engine-double-contractandcheck:where-matcher. All exit 0.check:slot-lookupmeasures 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.check:type-check-coveragegreen;check:type-check-debt --re-measurewas 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. Directtsc --noEmitonpackages/cloud-connectionreports 13, exactly its frozen DEBT, with zero in either file this PR touches — the contract types surfaced no error theanyhad been hiding.docs-audit/check-affected-docsgreen.check-adr-0087-registration,check-changeset-no-major,check-empty-changeset,check:changeset-gate-self-tests,check:objectui-changesetall green.packages/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.tsinobjectstack-ai/cloudpins the defect — thedatasets present but no replayer registeredline 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