Skip to content

feat(objectql): ADR-0048 — detect cross-package metadata collisions - #1796

Merged
xuyushun441-sys merged 1 commit into
mainfrom
feat/cross-package-metadata-collision
Jun 13, 2026
Merged

feat(objectql): ADR-0048 — detect cross-package metadata collisions#1796
xuyushun441-sys merged 1 commit into
mainfrom
feat/cross-package-metadata-collision

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Why

The metadata registry key is org/type/name with no package coordinate (refKey in packages/metadata-core/src/types.ts). Object names avoid collisions because the kernel namespace-prefix-validates them (they map to physical table names, and a duplicate CREATE TABLE fails loudly at the DB). But bare-named UI/automation metadata is not prefix-validatedpage, dashboard, flow, app, action, doc only require snake_case.

So two installed packages that each define e.g. a page named home produce the same logical key. In the objectql SchemaRegistry they sit under distinct composite keys (crm:home, hr:home), but bare-name read resolution silently picks one:

// getItem() — returns the FIRST composite key matching `:<name>`for(const[key,item]ofcollection){if(key.endsWith(`:${name}`))returnitemasT;}

getItem('page','home') returns whichever package registered first; the other's home is 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.ts
    • MetadataCollisionError (exported) — names both packages + type/name + fix.
    • isRealPackage — excludes absent provenance and the 'sys_metadata' rehydration sentinel.
    • Guard in registerItem: a real packageId registering a (type, name) already owned by a different real package → error. findOtherPackageOwner scans the live collection (same style as getItem/unregisterItem; no parallel index to drift across reset/unregister).
    • collisionPolicy: 'error' (default) | 'warn' option + OS_METADATA_COLLISION=warn env for deliberate migrations.
    • ObjectQL.registerApp and the nested-plugin loop already delegate to registerItem, so manifest + plugin metadata are both covered by the single choke point.
  • Not a collision (pass through untouched): same-package reloads, runtime/DB overlays (ADR-0005, bare-key or sys_metadata-sentinel rows), object ownership/extension, nav contributions (ADR-0029).
  • metadata-core is the conceptual root of the missing coordinate, but its optimistic-concurrency parentVersion check already rejects a blind base-layer double-create with ConflictError; 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_metadata sentinel, distinct names, cross-type same-name, warn policy.
  • engine-cross-package-collision.test.ts (e2e) — guard fires through real ObjectQL.registerApp; namespaced-apart and idempotent-reload pass.

Test results (this branch)

SuiteResult
@objectstack/objectql587 passed (46 files; +12 new)
@objectstack/metadata-core80 passed (6 files)
@objectstack/spec6542 passed (241 files)

ERROR/WARN lines in objectql output are pre-existing negative-path test logs, not failures.

Notes for reviewers

  • Default policy is 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=warn is the migration escape hatch.
  • Follow-ups (out of scope): CLI lint warning for non-prefixed bare-named metadata; prefix-strict spec validation for the next net-new bare-named type.

🤖 Generated with Claude Code

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>
@vercel

vercelBot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJun 12, 2026 9:09pm

Request Review

@github-actionsgithub-actionsBot added size/l documentation Improvements or additions to documentation tests tooling labels Jun 12, 2026
@xuyushun441-sys
xuyushun441-sys merged commit 6259882 into mainJun 13, 2026
12 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the feat/cross-package-metadata-collision branch June 13, 2026 01:34
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

Development

Successfully merging this pull request may close these issues.

2 participants

@xuyushun441-sys@os-zhuang