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(spec,runtime,metadata-protocol): discovery's "install this to enable" names a package that exists (#4093 follow-up) (#4204)
Discovery tells a consumer two things about an absent capability: that it is
absent, and what to do about it. The first has been carefully honest since
#2462/#4000 — the whole D12 lineage is about it. The second was invented from
the slot name, and nothing ever checked it.
The dispatcher templated `Install a ${slot} plugin to enable` across twelve
slots. metadata-protocol hand-wrote a table where TEN OF FIFTEEN entries named
a package that does not exist: plugin-redis, plugin-bullmq, job-scheduler,
plugin-notifications, plugin-storage, plugin-automation, ui-plugin, plus
plugin-ai / plugin-search / plugin-workflow for slots nothing implements at
all. The same value is surfaced as discovery's `provider`.
That is the same `declared ≠ enforced` failure this lineage has been closing,
one level over: not "does the capability exist" but "is the fix real". A
package that cannot be installed is a dead end handed to someone at the exact
moment they are trying to fix their stack, and an agent reading discovery
cannot tell it apart from a package it should install.
CORE_SERVICE_PROVIDER + serviceUnavailableMessage() in @objectstack/spec/system
are now the one place that sentence is written, read by both builders — so the
two hosts cannot name different packages for the same slot, the drift #4089 and
#4130 closed for the `metadata` and `data` entries. A parity test asserts it.
Entries were verified against what actually calls `registerService` for each
slot, not against name similarity. That is how `notification` turned out to be
filled by @objectstack/service-messaging — the one slot whose package shares no
word with its name, and the proof that a name-derived guess cannot be right in
general.
Four slots (ai, search, workflow, graphql) have no implementation anywhere, so
`null` is a first-class entry and the message says nothing ships rather than
naming a plausible package. `ui` keeps the fuller sentence from #4146 — it is
served by the `protocol` service and nothing registers the `ui` slot, so
"install X" alone would read as "X fills this slot" and leave the reader
staring at an empty slot afterwards. Moving it into the shared table is what
the parity test surfaced: the two builders agreed on the package but not on the
sentence, and the richer one deserved to win rather than be flattened.
scripts/check-service-providers.mjs (wired into the lint workflow as
check:service-providers) fails CI on either way this table rots: a name that is
not a real workspace package, or a CoreServiceName slot with no entry at all.
Both modes verified by temporarily breaking the table. Same spirit as
check-route-envelope, which caught a stale exemption of mine earlier in this
lineage — the guard has to outlive the fix, or the next rename re-introduces it.
Also: spec's api-surface snapshot regenerated (0 breaking, 2 added — the guard
reported exactly the two new exports); the two docs that printed the old remedy
corrected (`protocol/kernel/http-protocol.mdx` showed `Install plugin-workflow`
/ `Install plugin-ai` for slots that have no implementation, and
`kernel/services-checklist.mdx` carried the old template down to its `a auth`
grammar bug); and objectql's discovery test follows `provider` to the scoped
name — a `provider`/`message` assertion can live in any package that exercises
a discovery builder, and that one was caught by Test Core rather than by
filtered local runs.
Verified: spec 7143, runtime 947, metadata-protocol 110, objectql 1238 pass;
build 71/71; pnpm lint and all eleven check:* scripts clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Discovery's "install this to enable" now names a package that exists (#4093 follow-up).
8
+
9
+
Discovery tells a consumer two things about an absent capability: that it is absent, and what to do about it. The first has been carefully honest since #2462/#4000. The second was invented from the slot name.
10
+
11
+
The dispatcher templated `Install a ${slot} plugin to enable` across twelve slots, and `metadata-protocol` carried a hand-written table in which **ten of fifteen entries named a package that does not exist** — `plugin-redis`, `plugin-bullmq`, `job-scheduler`, `plugin-notifications`, `plugin-storage`, `plugin-automation`, `ui-plugin`, plus `plugin-ai`, `plugin-search` and `plugin-workflow` for slots nothing implements at all. That value is also surfaced as discovery's `provider`.
12
+
13
+
A remedy naming a package that cannot be installed is a dead end handed to someone at the exact moment they are trying to fix their stack — and an agent reading discovery cannot tell it apart from a package it should install. It is the same `declared ≠ enforced` failure this lineage has been closing, one level over: not "does the capability exist" but "is the fix real".
14
+
15
+
`CORE_SERVICE_PROVIDER` and `serviceUnavailableMessage()` in `@objectstack/spec/system` are now the one place that sentence is written, and both discovery builders read them, so the two hosts cannot tell a consumer to install different things (the drift #4089 and #4130 closed for the `metadata` and `data` entries). Entries were verified against what actually calls `registerService` for each slot rather than against name similarity — which is how `notification` turned out to be filled by `@objectstack/service-messaging`, the one slot whose package shares no word with its name.
16
+
17
+
Four slots — `ai`, `search`, `workflow`, `graphql` — have no implementation anywhere, so they now say so instead of naming a plausible package. `ui` keeps the fuller sentence it got in #4146 (`/ui` is served by the `protocol` service; nothing registers the `ui` slot), and that sentence now reaches both builders instead of one.
18
+
19
+
`scripts/check-service-providers.mjs` (wired into the lint workflow as `check:service-providers`) fails CI when a named package is not a real workspace package, or when a `CoreServiceName` slot has no entry — so a rename or a deletion cannot leave a stale instruction behind.
20
+
21
+
FROM → TO: `services.<slot>.message` and `services.<slot>.provider` change text for most unavailable slots. Anything matching on the old `Install a <slot> plugin to enable` wording should match on `status: 'unavailable'` instead — the status field is the contract; the message is prose for humans and agents.
"workflow": { "enabled": false, "status": "unavailable", "message": "No implementation ships for the 'workflow' slot — register a service under it to enable" },
58
+
"ai": { "enabled": false, "status": "unavailable", "message": "No implementation ships for the 'ai' slot — register a service under it to enable" }
0 commit comments