Found while fixing the 15.1 third-party eval P2 "hot-added object never becomes queryable" (framework#3108).
MetadataManager.register() (packages/metadata/src/metadata-manager.ts:280) updates the in-memory registry, persists to writable loaders, and publishes to the realtime service — but it never calls notifyWatchers(), so subscribe(type, cb) consumers are silent. Watchers only fire from the saveMetaItem path (metadata-manager.ts:1590) and NodeMetadataManager's file watcher.
Concretely: ObjectQL's registry bridge (subscribeToMetadataEvents, packages/objectql/src/plugin.ts) — the component that keeps the SchemaRegistry in sync — never hears about anything registered through register(), which is exactly what the artifact reload path (_parseAndRegisterArtifact) uses. #3108 works around this for the metadata:reloaded seam by carrying the parsed artifact on the event payload and ingesting from there, but the asymmetry remains for any other register() call site (e.g. a future cloud artifact-api mid-run refresh) and for any other subscribe() consumer.
Options:
- Make
register() emit the same MetadataWatchEvent as saveMetaItem (blast radius: boot-time registration would flood subscribers with N events — needs either a bulk/quiet mode for boot or subscribers that tolerate the flood).
- Keep
register() silent by design, document it loudly on the method, and require every bulk-ingest path to announce (as metadata:reloaded now does).
Either way the contract should be explicit — today it's a trap: subscribe() looks like it covers all writes, and it doesn't.
🤖 Generated with Claude Code
Found while fixing the 15.1 third-party eval P2 "hot-added object never becomes queryable" (framework#3108).
MetadataManager.register()(packages/metadata/src/metadata-manager.ts:280) updates the in-memory registry, persists to writable loaders, and publishes to the realtime service — but it never callsnotifyWatchers(), sosubscribe(type, cb)consumers are silent. Watchers only fire from thesaveMetaItempath (metadata-manager.ts:1590) and NodeMetadataManager's file watcher.Concretely: ObjectQL's registry bridge (
subscribeToMetadataEvents, packages/objectql/src/plugin.ts) — the component that keeps the SchemaRegistry in sync — never hears about anything registered throughregister(), which is exactly what the artifact reload path (_parseAndRegisterArtifact) uses. #3108 works around this for themetadata:reloadedseam by carrying the parsed artifact on the event payload and ingesting from there, but the asymmetry remains for any otherregister()call site (e.g. a future cloud artifact-api mid-run refresh) and for any othersubscribe()consumer.Options:
register()emit the sameMetadataWatchEventassaveMetaItem(blast radius: boot-time registration would flood subscribers with N events — needs either a bulk/quiet mode for boot or subscribers that tolerate the flood).register()silent by design, document it loudly on the method, and require every bulk-ingest path to announce (asmetadata:reloadednow does).Either way the contract should be explicit — today it's a trap:
subscribe()looks like it covers all writes, and it doesn't.🤖 Generated with Claude Code