fix(seed-loader): resolve seed references for marketplace-installed objects (engine-registry fallback)#3422
Merged
Merged
Conversation
…egistry (marketplace installs) Marketplace package objects register through the manifest service straight into the ObjectQL registry — after the boot-time bridgeObjectsToMetadataService pass — so the metadata service never lists them. buildDependencyGraph consulted only metadata.getObject(), got an empty reference graph for crm_*, and wrote every lookup/master_detail seed value verbatim (the raw externalId string, e.g. crm_contact.crm_account = 'Acme Corporation') instead of the target record's id. Under sharingModel: controlled_by_parent the dangling parent join then hid every contact from every user (REST total=0, GET 404) while the rows sat in the table. The loader now falls back to the engine's feature-detected getSchema() when the metadata service has no definition for a seeded object. Metadata stays the preferred source; contract-minimal engines keep the old behavior. Regression tests: a metadata-protocol unit spec (engine-registry-only objects resolve, metadata still wins when present, no-getSchema engines degrade as before) and a cloud-connection integration spec driving the real install endpoint + real SeedLoaderService, pinning that reference columns hold ids. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
added a commit
that referenced
this pull request
Jul 24, 2026
…ata service (#3428) Marketplace-installed template packages register through the manifest service on kernel:ready (rehydrate) or an HTTP request (install), but the one-shot SchemaRegistry-to-metadata bridge runs once during ObjectQLPlugin.start() - so their objects only ever reached the ObjectQL registry. Every IMetadataService consumer (AI describe_object, Studio object lists, metadata.listObjects) missed them; only the seed loader had grown an engine-side fallback (#3422). The manifest service's register now bridges the manifest's own objects (registry-resolved, so both objects forms and extension merges come out canonical with _packageId stamped) into the metadata service: - resolved at call time: at objectql init the metadata service may not be registered yet - register('object', name, obj, { notify: false }) - same #3112 rationale as the startup bridge: announcing would loop the definitions back through our own subscribe('object') handler and overwrite provenance with 'metadata-service' - entries it did not bridge itself are never clobbered; its own copy is refreshed on same-package re-install so a hot marketplace upgrade stays fresh - armed only after start() ran the one-shot bridge, and never on project kernels (same environmentId gate) - boot-time behavior is unchanged register returns the bridge promise; marketplace install/rehydrate await it so metadata reads right after a 200 are deterministic. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ld CI runners
The spec drives the real install handler, whose dynamic
import('@objectstack/runtime') alone can take several seconds on a fresh
runner — the 5s vitest default timed out on CI while passing warm locally.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Marketplace-installed template packages (e.g.
app.objectstack.hotcrm) seeded their lookup / master_detail reference fields with the raw externalId string instead of the target record's id:crm_contact.crm_accountandcrm_opportunity.crm_accountheld"Acme Corporation"rather than thecrm_accountrow id.The damage compounded under RLS:
crm_contactdeclaressharingModel: controlled_by_parent, whose row filter compiles to a join on the parent reference. With every reference dangling, the join matched nothing and the whole object went invisible to everyone — platform admins included (REST listtotal=0, single GET 404) while 5 rows sat in the SQLite table.Root cause
SeedLoaderService.buildDependencyGraphbuilt the reference graph frommetadata.getObject()only. Marketplace packages register their objects through themanifestservice straight into the ObjectQL registry (ql.registerApp) — and both the install endpoint and boot-time rehydrate run afterbridgeObjectsToMetadataServicedid its one-shot copy atkernel:ready. The metadata service therefore never lists crm_* objects, the reference graph came back empty, and every reference value fell through the "no known refs" path and was written verbatim.Showcase's own config-declared apps were unaffected because AppPlugin registers before the bridge runs.
Fix
buildDependencyGraphnow resolves object definitions via the metadata service first, then falls back to the engine's own schema registry (feature-detectedengine.getSchema(), which the ObjectQL engine exposes; theIDataEnginecontract is unchanged). Engines without a schema registry keep the old behavior. One change in@objectstack/metadata-protocolcovers every seed path (marketplace install/reseed, AppPlugin, dispatcher) since objectql/runtime re-export the same class.Regression tests
packages/metadata-protocol/src/seed-loader-engine-schema-fallback.test.ts— engine-registry-only objects resolve (master_detail, lookup, self-reference, dependency ordering); metadata still wins when present; engines withoutgetSchemadegrade exactly as before. Red-checked: reverting the fix fails the marketplace-scenario case.packages/cloud-connection/src/marketplace-install-local-seed-lookup.test.ts— drives the realPOST /api/v1/marketplace/install-localendpoint with the realSeedLoaderService(not mocked, unlike the reseed spec), a faithful engine stub, and a metadata service that has never heard of the package's objects; pins that reference columns hold parent ids, never the authored natural-key strings. Also red-checked.E2E verification
examples/app-showcase+ the shippedapp.objectstack.hotcrm@2.2.2install artifact on a fresh DB,objectstack dev --seed-admin(workspace CLI), signed in as the dev admin, reseeded, then:GET /api/v1/data/crm_contact→total: 5(was 0), everycrm_accountan internal idGET /api/v1/data/crm_contact/:id→ 200 (was 404), id maps back toAcme CorporationGET /api/v1/data/crm_opportunity→ 61 rows, 0 dangling account referencesinserted=162, errors=0Related: #3421 heals the missing rows case on rehydrate — orthogonal files, complementary fixes (once it lands, its heal path benefits from this resolution fix too).
🤖 Generated with Claude Code