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(core,plugin-dev,runtime): every in-memory fallback and dev stub self-describes honestly (#4058 step 1) (#4082)
ADR-0076 D12 gave services one standard way to say "I am not the real thing"
(`__serviceInfo`), but the producers never converged on it, and the result was
wrong in both directions:
- The kernel's OWN fallbacks (createMemoryCache/Queue/Job/I18n/Metadata) carried
`_fallback: true` — a marker NO consumer recognized, `readServiceSelfInfo`
included — so both discovery builders reported them as fully `available`.
They were absent from D12's inventory and were its worst case.
- plugin-dev marked every implementation with the same `_dev: true`, normalized
to `{ status: 'stub', handlerReady: false }`. That declared a working
in-memory search index exactly as fake as an AI stub returning invented text.
The single marker is why #4058's real question — should the dispatcher gate
other domains on `handlerReady`, as #4000 did for analytics? — could not be
answered: the two kinds of implementation in there were indistinguishable.
This change makes them distinguishable, and changes nothing else.
The classification, now one reviewable table (`DEV_STUB_SELF_INFO`):
- `degraded` — really does the work, with reduced capability: `cache`, `queue`,
`job`, `file-storage`, `search`, `i18n`, `metadata`, `workflow`, `realtime`.
Their answers are true answers; each `message` names what is missing —
including the ones easy to miss (memory-job's `schedule()` never fires: no
timer; the workflow stub validates no state machine).
- `stub` — the answer is fabricated: `ai`, `automation`, `notification`, `data`,
`auth`, `security.permissions`, `security.rls`, `security.fieldMasker`.
`handlerReady: false` answers the separate question — no HTTP handler serves
this — and is set for every `stub` plus the surfaceless `degraded` ones
(`cache` / `queue` / `job` / `realtime`, the D12 realtime precedent).
Also: the one duck-typed consumer of the ad-hoc markers (an i18n diagnostic in
app-plugin) now reads `readServiceSelfInfo`, and plugin-dev never overwrites a
self-description an implementation already carries — a wrapped kernel fallback
knows better than this plugin what it is.
No routing, gating, or dispatch behavior changes: every dispatcher domain
resolves services exactly as before. Discovery output does change, which is the
point — a kernel fallback that claimed `available` now reports `degraded` and
says why. `_dev` stays understood by `readServiceSelfInfo`, so third-party stubs
carrying it keep working.
#4058 step 2 (which domains adopt the `handlerReady` gate) stays open, now with
the two classes separable.
Every in-memory fallback and dev stub now self-describes with the standard `__serviceInfo` descriptor, classified by what it actually is (#4058 step 1).
8
+
9
+
ADR-0076 D12 gave services one way to say "I am not the real thing", but the producers never converged on it:
10
+
11
+
- The kernel's own fallbacks (`createMemoryCache` / `Queue` / `Job` / `I18n` / `Metadata`) carried `_fallback: true` — a marker **no** consumer recognized, `readServiceSelfInfo` included — so both discovery builders reported them as fully `available`.
12
+
-`plugin-dev` marked all of its implementations with the same `_dev: true`, normalized to `status: 'stub', handlerReady: false`. That declared a working in-memory search index exactly as fake as an AI stub returning invented text.
13
+
14
+
Both now carry `__serviceInfo`, split by a rule that holds across the whole set:
15
+
16
+
-**`degraded`** — really does the work, with reduced capability: `cache`, `queue`, `job`, `file-storage`, `search`, `i18n`, `metadata`, `workflow`, `realtime`. Its answers are true answers; the `message` names what is missing (no persistence, no scheduling timer, no state-machine validation, …).
17
+
-**`stub`** — the answer is fabricated: `ai`, `automation`, `notification`, `data`, `auth`, `security.permissions`, `security.rls`, `security.fieldMasker`. Never to be mistaken for a capability.
18
+
19
+
`handlerReady: false` is set independently wherever no HTTP handler serves the slot (`cache` / `queue` / `job` / `realtime`, and every `stub`).
20
+
21
+
Discovery output changes accordingly — a kernel fallback that used to report `status: 'available'` now reports `degraded` with an explanatory message. No routing, gating, or dispatch behavior changes: every dispatcher domain still resolves services exactly as before. Consumers reading `discovery.services.*` get the truth instead of a uniform claim.
22
+
23
+
For anything that duck-typed the old markers: `svc._fallback` / `svc._dev` → `readServiceSelfInfo(svc)` from `@objectstack/spec/api` (the legacy `_dev` key is still understood by that reader, so third-party stubs carrying it keep working).
Copy file name to clipboardExpand all lines: docs/adr/0076-objectql-core-tiering.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -132,9 +132,10 @@ Decision: each capability plugin registers its routes as a **normalized handler*
132
132
**Root cause of agents being misled.** Several plugins register stub / dev / fallback services under canonical names, and the discovery builder reports *any* present service as fully real: `runtime/http-dispatcher.ts`'s `svcAvailable` hardcodes `{ enabled: true, status: 'available', handlerReady: true }` for every registered service — it **ignores stub markers** (its own comment even says "handlerReady:false … may be served by a stub", but the code never computes it). So `discovery.services.*` claims capabilities that are only stubbed, and consumers (AI agents, the console) trust them. A dev AI stub advertised this way has already confused an agent.
133
133
134
134
**Inventory of current fakes / mis-reports:**
135
-
-`plugin-dev` registers ~8 dev stubs — `storage` / `search` / `automation` / `graphql` / `analytics` / `realtime` / `notification` / `ai` (they already carry a `_dev: true` marker that nothing respects). *(Update #4000: the `analytics` one is **gone** — after the fallback's retirement (#3891/#3989) it was the last thing refilling that slot, and refilling it re-created the retired shape in dev: the dispatcher gated on service presence, so the stub was called like an engine and answered 200 with fabricated rows. The remaining stubs are unchanged, and the class-wide question they raise is tracked separately — see conclusion 3 below.)*
135
+
- `plugin-dev` registers ~8 dev stubs — `storage` / `search` / `automation` / `graphql` / `analytics` / `realtime` / `notification` / `ai` (they already carry a `_dev: true` marker that nothing respects). *(Update #4000: the `analytics` one is **gone** — after the fallback's retirement (#3891/#3989) it was the last thing refilling that slot, and refilling it re-created the retired shape in dev: the dispatcher gated on service presence, so the stub was called like an engine and answered 200 with fabricated rows. The remaining stubs are unchanged, and the class-wide question they raise is tracked separately — see conclusion 3 below.)* *(Update #4058 step 1: the blanket `_dev: true` is **gone** too. Every dev implementation now carries the standard `__serviceInfo`, classified in one reviewable table (`DEV_STUB_SELF_INFO`) by what it actually is — `degraded` for the ones that really work with reduced capability (`file-storage` / `search` / `metadata` / `workflow` / `realtime`, plus the wrapped kernel fallbacks), `stub` for the ones whose answer is fabricated (`ai` / `automation` / `notification` / `data` / `auth` / `security.*`). The single marker made those two indistinguishable and declared the working ones fake, which is why "adopt the dispatcher gate everywhere?" could not be answered. The gates themselves are untouched — that is #4058 step 2.)*
136
136
-`ObjectQLPlugin` registers a ~66-line `analytics`**fallback** (the D10 note — deliberate, but it reports as fully available).
137
137
-`http-dispatcher.ts``svcAvailable` — the hardcode above.
138
+
-*(Added by #4058: the **kernel's own** fallbacks — `createMemoryCache` / `Queue` / `Job` / `I18n` / `Metadata`, auto-registered by ObjectKernel — were missing from this inventory and were the worst case in it: they carried `_fallback: true`, a marker **no** reader recognized (not even `readServiceSelfInfo`), so both discovery builders reported them as fully `available`. They now self-describe as `degraded` with `handlerReady: false` where no HTTP surface exists (`cache` / `queue` / `job`). The lone duck-typed consumer of `_fallback`/`_dev` — an i18n diagnostic in `app-plugin.ts` — reads `readServiceSelfInfo` instead.)*
138
139
139
140
**Decision — honest capabilities:**
140
141
1. A registered service that is a stub / dev / fallback MUST self-identify with a **standard marker** (standardise one; `_dev` is the existing precedent).
Copy file name to clipboardExpand all lines: packages/core/src/fallbacks/memory-i18n.ts
+10-1Lines changed: 10 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -123,7 +123,16 @@ export function createMemoryI18n() {
123
123
}
124
124
125
125
return{
126
-
_fallback: true,_serviceName: 'i18n',
126
+
// [#4058] `degraded` (ADR-0076 D12): translations, locale fallback and
127
+
// interpolation are all real — what is missing is persistence and the
128
+
// authoring surface service-i18n adds. `handlerReady` left at the
129
+
// `degraded` default (true): the dispatcher's `/i18n` domain does serve
130
+
// this implementation.
131
+
__serviceInfo: {
132
+
status: 'degraded'asconst,
133
+
message: 'In-memory translations — real lookup and locale fallback, but nothing is persisted. Register I18nServicePlugin from @objectstack/service-i18n for the full implementation.',
Copy file name to clipboardExpand all lines: packages/core/src/fallbacks/memory-job.ts
+12-1Lines changed: 12 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -6,11 +6,22 @@
6
6
* Implements the IJobService contract with basic schedule/cancel/trigger
7
7
* operations. Used by ObjectKernel as an automatic fallback when no real
8
8
* job plugin (e.g. Agenda / BullMQ) is registered.
9
+
*
10
+
* [#4058] `degraded` (ADR-0076 D12), with the missing half named in the
11
+
* message rather than left for a deployer to discover: `trigger()` really runs
12
+
* the registered handler, but nothing here owns a timer, so a `schedule()`d job
13
+
* NEVER fires on its own. That is reduced capability, not fabricated output —
14
+
* no call returns a made-up answer. `handlerReady: false`: no HTTP surface.
9
15
*/
10
16
exportfunctioncreateMemoryJob(){
11
17
constjobs=newMap<string,any>();
12
18
return{
13
-
_fallback: true,_serviceName: 'job',
19
+
__serviceInfo: {
20
+
status: 'degraded'asconst,
21
+
handlerReady: false,
22
+
message: 'In-process job registry — trigger() runs handlers, but scheduled jobs never fire on their own (no timer). Register a job plugin (e.g. Agenda) for real scheduling.',
0 commit comments