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] of collection) {
  if (key.endsWith(`:${name}`)) return item as T;
}

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)

Suite Result
@objectstack/objectql 587 passed (46 files; +12 new)
@objectstack/metadata-core 80 passed (6 files)
@objectstack/spec 6542 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

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 12, 2026 9:09pm

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Jun 12, 2026
@xuyushun441-sys
xuyushun441-sys merged commit 6259882 into main Jun 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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants