You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(metadata): register()/unregister() announce to subscribe() watchers (#3112) (#3131)
* fix(metadata): register()/unregister() announce to subscribe() watchers (#3112)
`MetadataManager.register()` updated the in-memory registry, persisted to
writable loaders and published to the realtime service — but never called
`notifyWatchers()`. `unregister()` had the same gap. Watchers only ever fired
from the `saveMetaItem` path (indirectly, via the sys_metadata repository watch
stream) and the filesystem watcher, so `subscribe()` looked like it covered
every write while silently missing all of them.
The consumer that pays for this is ObjectQL's SchemaRegistry bridge — the
component that decides what is queryable. #3108 worked around it for the
`metadata:reloaded` seam by carrying the parsed artifact on the event payload,
but the asymmetry remained for every other `register()` call site and every
other `subscribe()` consumer.
Announce by default, so a new call site is correct without knowing the contract
exists. Two concrete fixes fall out: `unregisterPackage()` now tells cached
consumers to drop uninstalled objects instead of serving them until restart,
and runtime datasource writes reach watchers.
Bulk ingest opts out explicitly via the new `MetadataWriteOptions`
(`{ notify: false }`) at the five boot/batch sites, each of which either runs
before consumers cache anything or announces the whole batch once. ObjectQL's
registry bridge MUST stay silent for a second reason: it copies objects OUT of
the SchemaRegistry, and announcing would feed them back through a handler that
re-registers under `_packageId ?? 'metadata-service'` — overwriting the true
package provenance of every object whose body carries no `_packageId`.
Additive only: the 3-arg forms keep working, and the api-surface ratchet reports
0 breaking / 1 added.
Tests pin both halves of the contract (announce by default, silence only when
asked) and the no-flood invariant on the reload path; both were verified to fail
against the pre-change behavior.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(changeset): state the register() notify change as a contract fix, not a bug fix
unregisterPackage()/bulkUnregister() have no production caller in framework or
cloud today, so their announce is correct-but-latent. The one live behavior
change is runtime datasource writes reaching the HMR SSE stream. Say so instead
of advertising two 'concrete fixes'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
**`MetadataManager.register()` / `unregister()` now announce to `subscribe()` watchers.** Both updated the registry, persisted to writable loaders and published to realtime, but never fired the watch callbacks — so `subscribe()` looked like it covered every write while silently missing all of them. Only the `saveMetaItem` path (via the repository watch stream) and the filesystem watcher ever reached a subscriber. Runtime consumers that cache metadata — notably ObjectQL's SchemaRegistry bridge, the component that decides what is queryable — went stale on every other write until the process restarted.
8
+
9
+
Announcing is now the **default**, so a new call site is correct without knowing this contract exists. This is a contract fix rather than a bug fix: the one live behavior change is that runtime datasource writes (`datasource-admin`) now reach the HMR SSE stream, which subscribes to every registered type. `unregisterPackage()` / `bulkUnregister()` also announce their deletes now — correct, but latent, since neither has a production caller today.
10
+
11
+
Bulk ingest opts out explicitly with the new `MetadataWriteOptions` (`{ notify: false }`) — boot-time filesystem priming, artifact ingest, and ObjectQL's registry bridge, each of which either runs before consumers cache anything or announces the whole batch once (as the artifact reload path does via `metadata:reloaded`). The bridge in particular MUST stay silent: it copies objects out of the SchemaRegistry, and announcing would feed them back through a handler that re-registers under `_packageId ?? 'metadata-service'`, overwriting the true package provenance of every object whose body carries no `_packageId`.
12
+
13
+
Additive only — `register(type, name, data)` and `unregister(type, name)` keep working unchanged.
0 commit comments