feat(objectql): ADR-0048 — detect cross-package metadata collisions#1796
Merged
Conversation
The metadata registry key is org/type/name with no package coordinate. Object names are kernel namespace-prefix-validated (they map to physical tables), but bare-named UI/automation metadata (page, dashboard, flow, app, action, doc, …) is only snake_case-validated. So two installed packages each defining e.g. a `page` named `home` collide on the same logical key, and bare-name read resolution (getItem) silently returns whichever registered first — last-write-wins with no diagnostic. Decision (ADR-0048): detect cross-package base-layer collisions at registration time and raise an explicit, actionable error naming both packages and the type/name — rather than retrofitting namespace-prefix enforcement onto every legacy bare-named type. Prefix stays a recommended convention; new types (e.g. doc) can be prefix-strict from day one. - registry: MetadataCollisionError + isRealPackage (excludes the 'sys_metadata' rehydration sentinel); guard in registerItem that refuses a real packageId registering a (type,name) already owned by a DIFFERENT real package; findOtherPackageOwner scans the live collection (no parallel index to drift). collisionPolicy 'error' (default) | 'warn', env OS_METADATA_COLLISION=warn for migrations. - Legitimate same-key writes pass through untouched: same-package reloads, runtime/DB overlays (ADR-0005), object ownership/extension, nav contributions. - tests: registry-cross-package-collision (unit) + engine-cross-package-collision (e2e through ObjectQL.registerApp). Suites green: objectql 587, metadata-core 80, spec 6542. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
3 tasks
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.
Why
The metadata registry key is
org/type/namewith no package coordinate (refKeyinpackages/metadata-core/src/types.ts). Object names avoid collisions because the kernel namespace-prefix-validates them (they map to physical table names, and a duplicateCREATE TABLEfails loudly at the DB). But bare-named UI/automation metadata is not prefix-validated —page,dashboard,flow,app,action,doconly require snake_case.So two installed packages that each define e.g. a
pagenamedhomeproduce the same logical key. In the objectqlSchemaRegistrythey sit under distinct composite keys (crm:home,hr:home), but bare-name read resolution silently picks one:getItem('page','home')returns whichever package registered first; the other'shomeis unreachable, with no error and no warning. This is last-write-wins, silent — worse than the object case which fails loudly. Surfaced during ADR-0046 (doc) review; generalised here into its own work item.Decision (ADR-0048)
Detect cross-package same-key collisions in the code-defined base layer at registration time and raise an explicit error naming both packages and the type/name — rather than retrofitting namespace-prefix enforcement onto every legacy bare-named type (large migration cost / broken cross-refs). Prefix stays a recommended convention (the error message suggests
<namespace>_<name>); brand-new types (e.g.doc) can be prefix-strict from day one.What changed
packages/objectql/src/registry.tsMetadataCollisionError(exported) — names both packages + type/name + fix.isRealPackage— excludes absent provenance and the'sys_metadata'rehydration sentinel.registerItem: a realpackageIdregistering a(type, name)already owned by a different real package → error.findOtherPackageOwnerscans the live collection (same style asgetItem/unregisterItem; no parallel index to drift acrossreset/unregister).collisionPolicy: 'error' (default) | 'warn'option +OS_METADATA_COLLISION=warnenv for deliberate migrations.ObjectQL.registerAppand the nested-plugin loop already delegate toregisterItem, so manifest + plugin metadata are both covered by the single choke point.sys_metadata-sentinel rows), object ownership/extension, nav contributions (ADR-0029).metadata-coreis the conceptual root of the missing coordinate, but its optimistic-concurrencyparentVersioncheck already rejects a blind base-layer double-create withConflictError; the genuinely silent path is the objectql read resolution, so enforcement lands there. No metadata-core code change.Tests
registry-cross-package-collision.test.ts(unit) — error + message contents, same-package reload, runtime overlay (both directions),sys_metadatasentinel, distinct names, cross-type same-name,warnpolicy.engine-cross-package-collision.test.ts(e2e) — guard fires through realObjectQL.registerApp; namespaced-apart and idempotent-reload pass.Test results (this branch)
@objectstack/objectql@objectstack/metadata-core@objectstack/specERROR/WARN lines in objectql output are pre-existing negative-path test logs, not failures.
Notes for reviewers
error(loud at registration). If a real cross-package clash exists in a downstream package set today, boot will now fail with an actionable message — that is the intended behavior;OS_METADATA_COLLISION=warnis the migration escape hatch.🤖 Generated with Claude Code